--- a/calendar/base/content/calendar-alarm-dialog.js
+++ b/calendar/base/content/calendar-alarm-dialog.js
@@ -39,17 +39,17 @@
Components.utils.import("resource://gre/modules/PluralForm.jsm");
/**
* Helper function to get the alarm service and cache it.
*
* @return The alarm service component
*/
function getAlarmService() {
- if (!window.mAlarmService) {
+ if (!("mAlarmService" in window)) {
window.mAlarmService = Components.classes["@mozilla.org/calendar/alarm-service;1"]
.getService(Components.interfaces.calIAlarmService);
}
return window.mAlarmService;
}
/**
* Event handler for the 'snooze' event. Snoozes the given alarm by the given
--- a/calendar/base/content/calendar-base-view.xml
+++ b/calendar/base/content/calendar-base-view.xml
@@ -349,16 +349,18 @@
onget="return document.getElementById('calendarviewBroadcaster')"/>
<property name="labeldaybox" readonly="true"
onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'labeldaybox')"/>
<property name="rotated"
onget="return (this.orient == 'horizontal')"
onset="return (this.orient = (val ? 'horizontal' : 'vertical'))"/>
+ <property name="supportsRotation" readonly="true"
+ onget="return false"/>
<property name="displayDaysOff"
onget="return this.mDisplayDaysOff;"
onset="return (this.mDisplayDaysOff = val);"/>
<property name="controller"
onget="return this.mController;"
onset="return (this.mController = val);"/>
<property name="daysOffArray"
onget="return this.mDaysOffArray;"
@@ -370,16 +372,18 @@
onget="return this.mShowCompleted;"
onset="return (this.mShowCompleted = val);"/>
<property name="timezone"
onget="return this.mTimezone;"
onset="return (this.mTimezone = val);"/>
<property name="workdaysOnly"
onget="return this.mWorkdaysOnly;"
onset="return (this.mWorkdaysOnly = val);"/>
+ <property name="supportsWorkdaysOnly" readonly="true"
+ onget="return true;"/>
<property name="selectionObserver" readonly="true"
onget="return this.mSelectionObserver;"/>
<property name="startDay" readonly="true"
onget="return this.startDate;"/>
<property name="endDay" readonly="true"
onget="return this.endDate;"/>
<property name="supportDisjointDates" readonly="true"
onget="return false;"/>
@@ -763,17 +767,17 @@
this.moveView(-1);
break;
case kKE.DOM_VK_PAGE_DOWN:
this.moveView(1);
break;
}
]]></handler>
<handler event="DOMMouseScroll"><![CDATA[
- if (event.shiftKey) {
+ if (event.shiftKey && getPrefSafe('calendar.view.mousescroll', true)) {
this.moveView(event.detail < 0 ? -1 : 1);
}
// Prevent default scroll handling
event.preventDefault();
]]></handler>
</handlers>
</binding>
--- a/calendar/base/content/calendar-common-sets.js
+++ b/calendar/base/content/calendar-common-sets.js
@@ -61,44 +61,65 @@ var calendarController = {
"calendar_import_command": true,
"calendar_export_command": true,
"calendar_export_selection_command": true,
"calendar_publish_selected_calendar_command": true,
"calendar_publish_calendar_command": true,
"calendar_publish_selected_events_command": true,
+ "calendar_view_next_command": true,
+ "calendar_view_prev_command": true,
+
+ "calendar_toggle_orientation_command": true,
+ "calendar_toggle_workdays_only_command": true,
+
"calendar_task_filter_command": true,
"calendar_reload_remote_calendars": true,
+ "calendar_show_unifinder_command": true,
+ "calendar_toggle_completed_command": true,
"calendar_percentComplete-0_command": true,
"calendar_percentComplete-25_command": true,
"calendar_percentComplete-50_command": true,
"calendar_percentComplete-75_command": true,
"calendar_percentComplete-100_command": true,
- "calendar_percentComplete-100_command2": true,
"calendar_priority-0_command": true,
"calendar_priority-9_command": true,
"calendar_priority-5_command": true,
"calendar_priority-1_command": true,
"calendar_general-priority_command": true,
+ "calendar_general-progress_command": true,
"calendar_task_category_command": true,
"cmd_cut": true,
"cmd_copy": true,
"cmd_paste": true,
"cmd_undo": true,
"cmd_redo": true,
"cmd_print": true,
"cmd_selectAll": true,
"cmd_pageSetup": true,
// Thunderbird commands
"cmd_printpreview": true,
"button_print": true,
"button_delete": true,
- "cmd_delete": true
+ "cmd_delete": true,
+ "cmd_properties": true,
+ "cmd_runJunkControls": true,
+ "cmd_deleteJunk": true,
+ "cmd_applyFilters": true,
+ "cmd_applyFiltersToSelection": true,
+ "cmd_goForward": true,
+ "cmd_goBack": true,
+
+ // Pseudo commands
+ "calendar_in_foreground": true,
+ "calendar_in_background": true,
+ "calendar_mode_calendar": true,
+ "calendar_mode_task": true
},
updateCommands: function cC_updateCommands() {
for (var command in this.commands) {
goUpdateCommand(command);
}
},
@@ -126,47 +147,74 @@ var calendarController = {
return this.selected_items_writable;
case "calendar_new_todo_command":
return this.writable && this.calendars_support_tasks;
case "calendar_modify_todo_command":
return this.todo_items_selected;
// This code is temporarily commented out due to
// bug 469684 Unifinder-todo: raising of the context menu fires blur-event
// this.todo_tasktree_focused;
+ case "calendar_edit_calendar_command":
+ return this.isCalendarInForeground();
case "calendar_task_filter_command":
return true;
case "calendar_delete_todo_command":
+ case "calendar_toggle_completed_command":
case "calendar_percentComplete-0_command":
case "calendar_percentComplete-25_command":
case "calendar_percentComplete-50_command":
case "calendar_percentComplete-75_command":
case "calendar_percentComplete-100_command":
- case "calendar_percentComplete-100_command2":
case "calendar_priority-0_command":
case "calendar_priority-9_command":
case "calendar_priority-5_command":
case "calendar_priority-1_command":
case "calendar_task_category_command":
+ case "calendar_general-progress_command":
case "calendar_general-priority_command":
- return this.writable &&
+ return this.isCalendarInForeground() &&
+ this.writable &&
this.todo_items_selected &&
this.todo_items_writable;
case "calendar_delete_calendar_command":
- return !this.last_calendar;
-
+ return this.isCalendarInForeground() && !this.last_calendar;
case "calendar_import_command":
return this.writable;
case "calendar_export_selection_command":
return this.item_selected;
-
+ case "calendar_toggle_orientation_command":
+ return this.isInMode("calendar") &&
+ currentView().supportsRotation;
+ case "calendar_toggle_workdays_only_command":
+ return this.isInMode("calendar") &&
+ currentView().supportsWorkdaysOnly;
case "calendar_publish_selected_events_command":
return this.item_selected;
case "calendar_reload_remote_calendar":
return !this.no_network_calendars && !this.offline;
+
+ // The following commands all just need the calendar in foreground,
+ // make sure you take care when changing things here.
+ case "calendar_view_next_command":
+ case "calendar_view_prev_command":
+ case "calendar_in_foreground":
+ return this.isCalendarInForeground();
+ case "calendar_in_background":
+ return !this.isCalendarInForeground();
+
+ // The following commands need calendar mode, be careful when
+ // changing things.
+ case "calendar_show_unifinder_command":
+ case "calendar_mode_calendar":
+ return this.isInMode("calendar");
+
+ case "calendar_mode_task":
+ return this.isInMode("task");
+
default:
if (this.defaultController && !this.isCalendarInForeground()) {
// The delete-button demands a special handling in mail-mode
// as it is supposed to delete an element of the focused pane
if (aCommand == "cmd_delete" || aCommand == "button_delete") {
var focusedElement = document.commandDispatcher.focusedElement;
if (focusedElement) {
if (focusedElement.getAttribute("id") == "agenda-listbox") {
@@ -200,16 +248,22 @@ var calendarController = {
case "cmd_redo":
goSetMenuValue(aCommand, 'valueDefault');
return canRedo();
case "cmd_printpreview":
return false;
case "button_delete":
case "cmd_delete":
return this.item_selected;
+ case "cmd_properties":
+ case "cmd_runJunkControls":
+ case "cmd_deleteJunk":
+ case "cmd_applyFilters":
+ case "cmd_applyFiltersToSelection":
+ return false;
}
if (aCommand in this.commands) {
// All other commands we support should be enabled by default
return true;
}
}
return false;
},
@@ -300,16 +354,31 @@ var calendarController = {
break;
case "calendar_publish_selected_events_command":
publishCalendarData();
break;
case "calendar_reload_remote_calendars":
getCompositeCalendar().refresh();
break;
+ case "calendar_show_unifinder_command":
+ toggleUnifinder();
+ break;
+ case "calendar_view_next_command":
+ currentView().moveView(1);
+ break;
+ case "calendar_view_prev_command":
+ currentView().moveView(-1);
+ break;
+ case "calendar_toggle_orientation_command":
+ toggleOrientation();
+ break;
+ case "calendar_toggle_workdays_only_command":
+ toggleWorkdaysOnly();
+ break;
default:
if (this.defaultController && !this.isCalendarInForeground()) {
// If calendar is not in foreground, let the default controller take
// care. If we don't have a default controller (i.e sunbird), just
// continue.
this.defaultController.doCommand(aCommand);
return;
}
@@ -337,16 +406,23 @@ var calendarController = {
PrintUtils.showPageSetup();
break;
case "button_print":
case "cmd_print":
calPrint();
break;
// Thunderbird commands
+ case "cmd_goForward":
+ currentView().moveView(1);
+ break;
+ case "cmd_goBack":
+ currentView().moveView(-1);
+ break;
+
// For these commands, nothing should happen in calendar mode.
case "cmd_printpreview":
case "button_delete":
case "cmd_delete":
default:
return;
}
}
@@ -357,16 +433,27 @@ var calendarController = {
},
isCalendarInForeground: function cC_isCalendarInForeground() {
// For sunbird, calendar is always in foreground. Otherwise check if
// we are in the correct mode.
return isSunbird() || (gCurrentMode && gCurrentMode != "mail");
},
+ isInMode: function cC_isInMode(mode) {
+ switch (mode) {
+ case "mail":
+ return !isCalendarInForeground();
+ case "calendar":
+ return isSunbird() || (gCurrentMode && gCurrentMode == "calendar");
+ case "task":
+ return !isSunbird() && (gCurrentMode && gCurrentMode == "task");
+ }
+ },
+
onSelectionChanged: function cC_onSelectionChanged(aEvent) {
var selectedItems = aEvent.detail;
calendarController.item_selected = selectedItems && (selectedItems.length > 0);
var selLength = (selectedItems === undefined ? 0 : selectedItems.length);
var selected_events_readonly = 0;
var selected_events_requires_network = 0;
if (selLength > 0) {
@@ -549,17 +636,16 @@ function injectCalendarCommandController
// we leave the current thread in order to re-enter the message loop.
var tbController = top.controllers.getControllerForCommand("cmd_undo");
if (!tbController) {
setTimeout(injectCalendarCommandController, 0);
return;
} else {
calendarController.defaultController = tbController;
- ltnInitializeMenus();
}
}
top.controllers.insertControllerAt(0, calendarController);
document.commandDispatcher.updateCommands("calendar_commands");
}
/**
* Remove the calendar command controller from the document.
--- a/calendar/base/content/calendar-common-sets.xul
+++ b/calendar/base/content/calendar-common-sets.xul
@@ -79,52 +79,61 @@
<command id="calendar_export_selection_command" oncommand="goDoCommand('calendar_export_selection_command')"/>
<command id="calendar_publish_selected_calendar_command" oncommand="goDoCommand('calendar_publish_selected_calendar_command')"/>
<command id="calendar_publish_calendar_command" oncommand="goDoCommand('calendar_publish_calendar_command')"/>
<command id="calendar_publish_selected_events_command" oncommand="goDoCommand('calendar_publish_selected_events_command')"/>
<command id="calendar_reload_remote_calendars" oncommand="goDoCommand('calendar_reload_remote_calendars')"/>
- <command id="calendar_show_unifinder_command" oncommand="toggleUnifinder()"/>
+ <command id="calendar_show_unifinder_command" oncommand="goDoCommand('calendar_show_unifinder_command')"/>
<!-- The dash instead of the underscore is intended. the 'xxx-view' part should be the id of the view in the deck -->
<command id="calendar_day-view_command" oncommand="showCalendarView('day')"/>
<command id="calendar_week-view_command" oncommand="showCalendarView('week')"/>
<command id="calendar_multiweek-view_command" oncommand="showCalendarView('multiweek')"/>
<command id="calendar_month-view_command" oncommand="showCalendarView('month')"/>
<command id="calendar_task_category_command" oncommand="contextChangeTaskCategory(event);"/>
+ <command id="calendar_toggle_completed_command" oncommand="toggleCompleted(event)"/>
<command id="calendar_percentComplete-0_command" oncommand="contextChangeTaskProgress(event, 0)"/>
<command id="calendar_percentComplete-25_command" oncommand="contextChangeTaskProgress(event, 25)"/>
<command id="calendar_percentComplete-50_command" oncommand="contextChangeTaskProgress(event, 50)"/>
<command id="calendar_percentComplete-75_command" oncommand="contextChangeTaskProgress(event, 75)"/>
<command id="calendar_percentComplete-100_command" oncommand="contextChangeTaskProgress(event, 100)"/>
- <command id="calendar_percentComplete-100_command2" oncommand="contextChangeTaskProgress2(event, 100)"/>
<command id="calendar_priority-0_command" oncommand="contextChangeTaskPriority(event, 0)"/>
<command id="calendar_priority-9_command" oncommand="contextChangeTaskPriority(event, 9)"/>
<command id="calendar_priority-5_command" oncommand="contextChangeTaskPriority(event, 5)"/>
<command id="calendar_priority-1_command" oncommand="contextChangeTaskPriority(event, 1)"/>
<command id="calendar_general-priority_command" oncommand="goDoCommand('calendar_general-priority_command')"/>
- <command id="calendar_toggle_orientation_command" persist="checked" oncommand="toggleOrientation()" disabled="true"/>
- <command id="calendar_toggle_workdays_only_command" persist="checked" oncommand="toggleWorkdaysOnly()"/>
+ <command id="calendar_general-progress_command" oncommand="goDoCommand('calendar_general-priority_command')"/>
+ <command id="calendar_toggle_orientation_command" persist="checked" oncommand="goDoCommand('calendar_toggle_orientation_command')"/>
+ <command id="calendar_toggle_workdays_only_command" persist="checked" oncommand="goDoCommand('calendar_toggle_workdays_only_command')"/>
<command id="calendar_toggle_tasks_in_view_command" persist="checked" oncommand="toggleTasksInView()"/>
<command id="calendar_toggle_show_completed_in_view_command" persist="checked" oncommand="toggleShowCompletedInView()"/>
<command id="calendar_toggle_minimonthpane_command" oncommand="document.getElementById('minimonth-pane').togglePane(event)"/>
<command id="calendar_toggle_calendarlist_command" oncommand="document.getElementById('calendar-list-pane').togglePane(event)"/>
<command id="calendar_task_filter_command" oncommand="taskViewUpdate(event.explicitOriginalTarget.getAttribute('value'))"/>
<command id="calendar_toggle_filter_command" oncommand="document.getElementById('task-filter-pane').togglePane(event)"/>
- <command id="calendar_view_next_command" oncommand="currentView().moveView(1)"/>
+ <command id="calendar_view_next_command" oncommand="goDoCommand('calendar_view_next_command')"/>
<command id="calendar_view_today_command" oncommand="currentView().moveView()"/>
- <command id="calendar_view_prev_command" oncommand="currentView().moveView(-1)"/>
- <command id="calendar_go_to_today_command" oncommand="goToDate(now())"/>
+ <command id="calendar_view_prev_command" oncommand="goDoCommand('calendar_view_prev_command')"/>
+
+ <!-- this is a pseudo-command that is disabled when in calendar mode -->
+ <command id="calendar_in_foreground"/>
+ <!-- this is a pseudo-command that is disabled when not in calendar mode -->
+ <command id="calendar_in_background"/>
+
+ <!-- These commands are enabled when in calendar or task mode, respectively -->
+ <command id="calendar_mode_calendar"/>
+ <command id="calendar_mode_task"/>
</commandset>
<keyset id="calendar-keys">
#ifdef XP_MACOSX
- // The following Mac specific code-lines are necessary, because you can't
- // just use the OPTION key on Mac OSX. So we will use SHIFT+OPTION on the
+ // The following Mac specific code-lines are necessary, because you can't
+ // just use the OPTION key on Mac OSX. So we will use SHIFT+OPTION on the
// Mac, but just ALT on all other platforms. See bug 448946.
<key id="calendar-day-view-key" key="&calendar.dayView.key;" observes="calendar_day-view_command" modifiers="shift alt"/>
<key id="calendar-week-view-key" key="&calendar.weekView.key;" observes="calendar_week-view_command" modifiers="shift alt"/>
<key id="calendar-multiweek-view-key" key="&calendar.multiweekView.key;" observes="calendar_multiweek-view_command" modifiers="shift alt"/>
<key id="calendar-month-view-key" key="&calendar.monthView.key;" observes="calendar_month-view_command" modifiers="shift alt"/>
#else
<key id="calendar-day-view-key" key="&calendar.dayView.key;" observes="calendar_day-view_command" modifiers="alt"/>
<key id="calendar-week-view-key" key="&calendar.weekView.key;" observes="calendar_week-view_command" modifiers="alt"/>
@@ -276,17 +285,18 @@
key="calendar-new-todo-key"
command="calendar_new_todo_command"
observes="calendar_new_todo_command"/>
<menuseparator id="calendar-menuseparator-beforemarkcompleted"/>
<menuitem id="calendar-context-markcompleted"
type="checkbox"
label="&calendar.context.markcompleted.label;"
accesskey="&calendar.context.markcompleted.accesskey;"
- oncommand="toggleCompleted(event)"/>
+ observes="calendar_toggle_completed_command"
+ command="calendar_toggle_completed_command"/>
<menu id="task-context-menu-progress"
label="&calendar.context.progress.label;"
accesskey="&calendar.context.progress.accesskey;">
<menupopup id="progress-menupopup" type="task-progress"/>
</menu>
<menu id="task-context-menu-priority"
label="&calendar.context.priority.label;"
accesskey="&calendar.context.priority.accesskey;">
--- a/calendar/base/content/calendar-dialog-utils.js
+++ b/calendar/base/content/calendar-dialog-utils.js
@@ -179,30 +179,45 @@ function recurrenceRule2String(recurrenc
let monthlyString = getRString("monthlyLastDayOfNth");
ruleString = PluralForm.get(rule.interval, monthlyString)
.replace("#1", rule.interval);
} else {
// we don't support any other combination for now...
return getRString("ruleTooComplex");
}
} else {
- let day_string = "";
- for (let i = 0; i < component.length; i++) {
- day_string += component[i];
- if (component.length > 1 &&
- i == (component.length - 2)) {
- day_string += ' ' + getRString("repeatDetailsAnd") + ' ';
- } else if (i < component.length-1) {
- day_string += ', ';
+ if (component.length == 31 &&
+ component.every(function (element, index, array) {
+ for (let i = 0; i < array.length; i++) {
+ if ((index + 1) == array[i]) {
+ return true;
+ }
+ }
+ return false;
+ })) {
+ // i.e. every day every N months
+ ruleString = getRString("monthlyEveryDayOfNth");
+ ruleString = PluralForm.get(rule.interval, ruleString)
+ .replace("#2", rule.interval);
+ } else {
+ // i.e. one or more monthdays every N months
+ let day_string = "";
+ for (let i = 0; i < component.length; i++) {
+ day_string += component[i];
+ if (component.length > 1 &&
+ i == (component.length - 2)) {
+ day_string += ' ' + getRString("repeatDetailsAnd") + ' ';
+ } else if (i < component.length-1) {
+ day_string += ', ';
+ }
}
+ let monthlyString = getRString("monthlyDayOfNth", [day_string]);
+ ruleString = PluralForm.get(rule.interval, monthlyString)
+ .replace("#2", rule.interval);
}
- let monthlyString = getRString("monthlyDayOfNth", [day_string]);
- ruleString = PluralForm.get(rule.interval, monthlyString)
- .replace("#2", rule.interval);
-
}
} else {
let monthlyString = getRString("monthlyDayOfNth", [startDate.day]);
ruleString = PluralForm.get(rule.interval, monthlyString)
.replace("#2", rule.interval);
}
} else if (rule.type == 'YEARLY') {
if (checkRecurrenceRule(rule, ['BYMONTH']) &&
--- a/calendar/base/content/calendar-dnd-listener.js
+++ b/calendar/base/content/calendar-dnd-listener.js
@@ -17,16 +17,17 @@
* Joey Minta <jminta@gmail.com>
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Michael Buettner <michael.buettner@sun.com>
* Philipp Kewisch <mozilla@kewis.ch>
* Berend Cornelius <berend.cornelius@sun.com>
+ * Martin Schroeder <mschroeder@mozilla.x-home.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
@@ -40,70 +41,50 @@
Components.utils.import("resource://calendar/modules/calUtils.jsm");
Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
var itemConversion = {
/**
* Converts an email message to a calendar item.
*
- * XXX Currently, only the title is taken from the passed message. Aside
- * from that, the currently visible message in the preview pane is used.
- *
* @param aItem The target calIItemBase.
- * @param aMessage The message to convert from
+ * @param aMessage The nsIMsgHdr to convert from.
*/
- calendarItemFromMessage: function iC_calendarItemFromMessage(aItem, aMessage) {
+ calendarItemFromMessage: function iC_calendarItemFromMessage(aItem, aMsgHdr) {
+ let msgFolder = aMsgHdr.folder;
+ let msgUri = msgFolder.getUriForMsg(aMsgHdr);
+
aItem.calendar = getSelectedCalendar();
- aItem.title = aMessage.mime2DecodedSubject;
+ aItem.title = aMsgHdr.mime2DecodedSubject;
- setDefaultStartEndHour(aItem);
+ cal.setDefaultStartEndHour(aItem);
cal.alarms.setDefaultValues(aItem);
- // XXX It would be great if nsPlainTextParser could take care of this.
- function htmlToPlainText(html) {
- var texts = html.split(/(<\/?[^>]+>)/);
- var text = texts.map(function hTPT_map(string) {
- if (string.length > 0 && string[0] == '<') {
- var regExpRes = string.match(/^<img.*?alt\s*=\s*['"](.*)["']/i)
- if (regExpRes) {
- return regExpRes[1];
- } else {
- return "";
- }
- } else {
- return string.replace(/&([^;]+);/g, function hTPT_replace(str, p1) {
- switch (p1) {
- case "nbsp": return " ";
- case "amp": return "&";
- case "lt": return "<";
- case "gt": return ">";
- case "quot": return '\"';
- }
- return " ";
- });
- }
- }).join("");
+ let messenger = Components.classes["@mozilla.org/messenger;1"]
+ .createInstance(Components.interfaces.nsIMessenger);
+ let streamListener = Components.classes["@mozilla.org/network/sync-stream-listener;1"]
+ .createInstance(Components.interfaces.nsISyncStreamListener);
+ messenger.messageServiceFromURI(msgUri).streamMessage(msgUri,
+ streamListener,
+ null,
+ null,
+ false,
+ "",
+ false);
- return text;
- }
-
- var content = document.getElementById("messagepane");
- if (content) {
- var messagePrefix = /^mailbox-message:|^imap-message:|^news-message:/i;
- if (messagePrefix.test(GetLoadedMessage())) {
- var message = content.contentDocument;
- var body = message.body;
- if (body) {
- aItem.setProperty(
- "DESCRIPTION",
- htmlToPlainText(body.innerHTML));
- }
- }
- }
+ let plainTextMessage = "";
+ plainTextMessage = msgFolder.getMsgTextFromStream(streamListener.inputStream,
+ aMsgHdr.Charset,
+ 65536,
+ 32768,
+ false,
+ true,
+ {});
+ aItem.setProperty("DESCRIPTION", plainTextMessage);
},
/**
* Copy base item properties from aItem to aTarget. This includes properties
* like title, location, description, priority, transparency,
* attendees, categories, calendar, recurrence and possibly more.
*
* @param aItem The item to copy from.
--- a/calendar/base/content/calendar-management.js
+++ b/calendar/base/content/calendar-management.js
@@ -34,49 +34,47 @@
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* Get this window's currently selected calendar.
- *
+ *
* @return The currently selected calendar.
*/
function getSelectedCalendar() {
return getCompositeCalendar().defaultCalendar;
}
/**
* Deletes the passed calendar, prompting the user if he really wants to do
* this. If there is only one calendar left, no calendar is removed and the user
* is not prompted.
*
* @param aCalendar The calendar to delete.
*/
function promptDeleteCalendar(aCalendar) {
- var calendars = getCalendarManager().getCalendars({});
+ let calendars = getCalendarManager().getCalendars({});
if (calendars.length <= 1) {
// If this is the last calendar, don't delete it.
return;
}
- var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Components.interfaces.nsIPromptService);
- var ok = promptService.confirm(
- window,
- calGetString("calendar", "unsubscribeCalendarTitle"),
- calGetString("calendar",
- "unsubscribeCalendarMessage",
- [aCalendar.name]),
- {});
+ let promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+ let ok = promptService.confirm(window,
+ calGetString("calendar", "unsubscribeCalendarTitle"),
+ calGetString("calendar", "unsubscribeCalendarMessage",
+ [aCalendar.name]),
+ {});
if (ok) {
- var calMgr = getCalendarManager();
+ let calMgr = cal.getCalendarManager();
calMgr.unregisterCalendar(aCalendar);
calMgr.deleteCalendar(aCalendar);
}
}
/**
* Called to initialize the calendar manager for a window.
*/
@@ -117,18 +115,17 @@ function initHomeCalendar() {
return homeCalendar;
}
/**
* Called to clean up the calendar manager for a window.
*/
function unloadCalendarManager() {
- var composite = getCompositeCalendar();
- composite.setStatusObserver(null, null);
+ getCompositeCalendar().setStatusObserver(null, null);
}
/**
* Updates the sort order preference based on the given event. The event is a
* "SortOrderChanged" event, emitted from the calendar-list-tree binding. You
* can also pass in an object like { sortOrder: "Space separated calendar ids" }
*
* @param event The SortOrderChanged event described above.
@@ -248,37 +245,38 @@ function openCalendarSubscriptionsDialog
"chrome,titlebar,modal,resizable");
}
/**
* Calendar Offline Manager
*/
var calendarOfflineManager = {
QueryInterface: function cOM_QueryInterface(aIID) {
- return doQueryInterface(this, calendarOfflineManager.prototype, aIID,
- [Components.interfaces.nsIObserver, Components.interfaces.nsISupports]);
+ return cal.doQueryInterface(this, null, aIID,
+ [Components.interfaces.nsIObserver,
+ Components.interfaces.nsISupports]);
},
init: function cOM_init() {
if (this.initialized) {
throw Components.results.NS_ERROR_ALREADY_INITIALIZED;
}
- var os = Components.classes["@mozilla.org/observer-service;1"]
+ let os = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
os.addObserver(this, "network:offline-status-changed", false);
this.updateOfflineUI(!this.isOnline());
this.initialized = true;
},
uninit: function cOM_uninit() {
if (!this.initialized) {
throw Components.results.NS_ERROR_NOT_INITIALIZED;
}
- var os = Components.classes["@mozilla.org/observer-service;1"]
+ let os = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
os.removeObserver(this, "network:offline-status-changed", false);
this.initialized = false;
},
isOnline: function cOM_isOnline() {
return (!getIOService().offline);
--- a/calendar/base/content/calendar-menus.xml
+++ b/calendar/base/content/calendar-menus.xml
@@ -163,9 +163,9 @@
<children/>
</content>
<implementation>
<constructor><![CDATA[
this.mType = "priority";
]]></constructor>
</implementation>
</binding>
-</bindings>
\ No newline at end of file
+</bindings>
--- a/calendar/base/content/calendar-month-view.xml
+++ b/calendar/base/content/calendar-month-view.xml
@@ -19,20 +19,21 @@
- Oracle Corporation
- Portions created by the Initial Developer are Copyright (C) 2005
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- Vladimir Vukicevic <vladimir@pobox.com>
- Stefan Sitter <ssitter@googlemail.com>
- Clint Talbert <cmtalbert@myfastmail.com>
- - Michael Büttner <michael.buettner@sun.com>
+ - Michael Buettner <michael.buettner@sun.com>
- Philipp Kewisch <mozilla@kewis.ch>
- Markus Adrario <MarkusAdrario@web.de>
- Berend Cornelius <berend.cornelius@sun.com>
+ - Martin Schroeder <mschroeder@mozilla.x-home.org>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
@@ -50,60 +51,68 @@
<bindings id="calendar-month-view-bindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="calendar-month-day-box-item" extends="chrome://calendar/content/calendar-view-core.xml#calendar-editable-item">
- <content tooltip="itemTooltip">
+ <content mousethrough="never" tooltip="itemTooltip">
<xul:vbox flex="1">
<xul:hbox>
<xul:box anonid="event-container"
class="calendar-color-box"
xbl:inherits="calendar-uri,calendar-id"
flex="1">
<xul:box class="calendar-event-selection" orient="horizontal" flex="1">
<xul:stack anonid="eventbox"
class="calendar-event-box-container"
xbl:inherits="readonly,flashing,alarm,allday,priority,progress,status,calendar,categories"
flex="1">
<xul:hbox class="calendar-event-details">
- <xul:image anonid="item-icon"
- class="calendar-month-day-box-item-image"
- xbl:inherits="progress,allday"/>
+ <xul:vbox pack="center">
+ <xul:image anonid="item-icon"
+ class="calendar-month-day-box-item-image"
+ xbl:inherits="progress,allday"/>
+ </xul:vbox>
<xul:label anonid="item-label"
class="calendar-month-day-box-item-label"
xbl:inherits="context"/>
<xul:vbox align="left"
flex="1"
xbl:inherits="context">
<xul:label anonid="event-name"
crop="end"
flex="1"
- style="margin: 0;"/>
+ style="margin: 0;">
+ <html:div anonid="event-name-div"/>
+ </xul:label>
<xul:textbox anonid="event-name-textbox"
class="plain calendar-event-name-textbox"
crop="end"
hidden="true"
wrap="true"/>
<xul:spacer flex="1"/>
</xul:vbox>
- <xul:hbox anonid="alarm-icons-box"
- class="alarm-icons-box"
- align="center"
- xbl:inherits="flashing"/>
+ <xul:stack>
+ <xul:calendar-category-box anonid="category-box" xbl:inherits="categories" pack="end"/>
+ <xul:hbox anonid="alarm-icons-box"
+ class="alarm-icons-box"
+ align="center"
+ pack="end"
+ xbl:inherits="flashing"/>
+ </xul:stack>
</xul:hbox>
<xul:image anonid="gradient"
class="calendar-event-box-gradient"
- height="1px"/>
+ height="1px"
+ mousethrough="always"/>
</xul:stack>
</xul:box>
- <xul:calendar-category-box anonid="category-box" xbl:inherits="categories"/>
</xul:box>
</xul:hbox>
</xul:vbox>
</content>
<implementation>
<property name="occurrence">
<getter><![CDATA[
return this.mOccurrence;
@@ -1059,20 +1068,22 @@
delete this.mFlashingEvents[aAlarmItem.hashId];
}
]]></body>
</method>
</implementation>
<handlers>
<handler event="DOMMouseScroll"><![CDATA[
+ let scrollEnabled = getPrefSafe('calendar.view.mousescroll', true);
if (!event.ctrlKey &&
!event.shiftKey &&
!event.altKey &&
- !event.metaKey) {
+ !event.metaKey &&
+ scrollEnabled) {
// In the month view, the only thing that can be scrolled
// is the month the user is in. calendar-base-view takes care
// of the shift key, so only move the view when no modifier
// is pressed.
this.moveView(event.detail < 0 ? -1 : 1);
}
]]></handler>
</handlers>
--- a/calendar/base/content/calendar-multiday-view.xml
+++ b/calendar/base/content/calendar-multiday-view.xml
@@ -23,16 +23,17 @@
- Contributor(s):
- Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
- Thomas Benisch <thomas.benisch@sun.com>
- Dan Mosedale <dan.mosedale@oracle.com>
- Michael Buettner <michael.buettner@sun.com>
- Philipp Kewisch <mozilla@kewis.ch>
- Markus Adrario <MarkusAdrario@web.de>
- Berend Cornelius <berend.cornelius@sun.com>
+ - Martin Schroeder <mschroeder@mozilla.x-home.org>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
@@ -1280,17 +1281,17 @@
var dragState = col.mDragState;
col.fgboxes.box.setAttribute("dragging", "true");
col.fgboxes.dragbox.setAttribute("dragging", "true");
// check if we need to jump a column
if (dragState.dragType == "move") {
- newcol = col.calendarView.findColumnForClientPoint(event.screenX, event.screenY);
+ let newcol = col.calendarView.findColumnForClientPoint(event.screenX, event.screenY);
if (newcol && newcol != col) {
// kill our drag state
col.fgboxes.dragbox.removeAttribute("dragging");
col.fgboxes.box.removeAttribute("dragging");
// jump ship
newcol.acceptInProgressSweep(dragState);
@@ -1891,53 +1892,58 @@
]]></handler>
</handlers>
</binding>
<!--
- An individual event box, to be inserted into a column.
-->
<binding id="calendar-event-box" extends="chrome://calendar/content/calendar-view-core.xml#calendar-editable-item">
- <content tooltip="itemTooltip" mousethrough="never">
+ <content mousethrough="never" tooltip="itemTooltip">
<xul:box xbl:inherits="orient,width,height" flex="1">
<xul:box anonid="event-container"
class="calendar-color-box"
xbl:inherits="orient,readonly,flashing,alarm,allday,priority,progress,status,calendar,categories,calendar-uri,calendar-id"
flex="1">
<xul:box class="calendar-event-selection" orient="horizontal" flex="1">
<xul:stack anonid="eventbox"
align="stretch"
class="calendar-event-box-container"
flex="1"
xbl:inherits="context,parentorient=orient,readonly,flashing,alarm,allday,priority,progress,status,calendar,categories">
<xul:image flex="1" class="calendar-event-box-gradient"/>
<xul:vbox class="calendar-event-details" anonid="calendar-event-details">
- <xul:description anonid="event-name" class="calendar-event-details-core" flex="1"/>
+ <xul:description anonid="event-name" class="calendar-event-details-core" flex="1">
+ <html:div anonid="event-name-div"/>
+ </xul:description>
+
<xul:textbox anonid="event-name-textbox"
class="plain calendar-event-details-core calendar-event-name-textbox"
flex="1"
hidden="true"
wrap="true"/>
</xul:vbox>
- <xul:hbox pack="end">
- <xul:hbox anonid="alarm-icons-box"
- class="alarm-icons-box"
- pack="end"
- align="top"
- xbl:inherits="flashing"/>
- <xul:calendar-category-box anonid="category-box" xbl:inherits="categories" pack="end"/>
- </xul:hbox>
+ <xul:stack mousethrough="always">
+ <xul:calendar-category-box anonid="category-box" xbl:inherits="categories" pack="end" />
+ <xul:hbox anonid="alarm-icons-box"
+ class="alarm-icons-box"
+ pack="end"
+ align="top"
+ xbl:inherits="flashing"/>
+ </xul:stack>
<xul:box xbl:inherits="orient">
<xul:calendar-event-gripbar anonid="gripbar1"
class="calendar-event-box-grippy-top"
+ mousethrough="never"
whichside="start"
xbl:inherits="parentorient=orient"/>
- <xul:spacer flex="1"/>
+ <xul:spacer mousethrough="always" flex="1"/>
<xul:calendar-event-gripbar anonid="gripbar2"
class="calendar-event-box-grippy-bottom"
+ mousethrough="never"
whichside="end"
xbl:inherits="parentorient=orient"/>
</xul:box>
<!-- Do not insert anything here, otherwise the event boxes will
not be resizable using the gripbars. If you want to insert
additional elements, do so above the box with the gripbars. -->
</xul:stack>
</xul:box>
@@ -2020,29 +2026,34 @@
return 0;
var endDate = this.mOccurrence.endDate || this.mOccurrence.dueDate;
return endDate.hour * 60 + endDate.minute;
]]></getter>
</property>
<method name="setEditableLabel">
<body><![CDATA[
- var evl = this.eventNameLabel;
- var item = this.mOccurrence;
+ let evldiv = document.getAnonymousElementByAttribute(this, "anonid", "event-name-div");
+ let item = this.mOccurrence;
if (item.title && item.title != "") {
// Use <description> textContent so it can wrap.
- evl.textContent = item.title;
+ evldiv.textContent = item.title;
} else {
- evl.textContent = calGetString("calendar", "eventUntitled");
+ evldiv.textContent = calGetString("calendar", "eventUntitled");
}
var gripbar = document.getAnonymousElementByAttribute(this, "anonid", "gripbar1").boxObject.height;
var height = document.getAnonymousElementByAttribute(this, "anonid", "eventbox").boxObject.height;
- evl.setAttribute("style", "max-height: " + Math.max(0, height-gripbar*2) + "px");
+
+ if(this.orient == "vertical") {
+ evldiv.setAttribute("style", "max-height: " + Math.max(0, height-gripbar*2) + "px");
+ } else {
+ evldiv.setAttribute("style", "max-height: " + height + "px");
+ }
]]></body>
</method>
</implementation>
<handlers>
<handler event="mousedown" button="0"><![CDATA[
event.stopPropagation();
@@ -2176,16 +2187,19 @@
]]></constructor>
<property name="daysInView" readonly="true">
<getter><![CDATA[
return this.labeldaybox.childNodes && this.labeldaybox.childNodes.length;
]]></getter>
</property>
+ <property name="supportsRotation" readonly="true"
+ onget="return true"/>
+
<method name="handlePreference">
<parameter name="aSubject"/>
<parameter name="aTopic"/>
<parameter name="aPreference"/>
<body><![CDATA[
aSubject.QueryInterface(Components.interfaces.nsIPrefBranch2);
switch (aPreference) {
--- a/calendar/base/content/calendar-task-tree.js
+++ b/calendar/base/content/calendar-task-tree.js
@@ -91,41 +91,38 @@ function addCategoryNames(aEvent) {
}
/**
* Change the opening context menu for the selected tasks.
*
* @param aEvent The popupshowing event of the opening menu.
*/
function changeContextMenuForTask(aEvent) {
- var tasks = getSelectedTasks(aEvent);
- var task = null;
- var tasksSelected = (tasks.length > 0);
+ let tasksSelected = (getSelectedTasks(aEvent).length > 0);
applyAttributeToMenuChildren(aEvent.target, "disabled", (!tasksSelected));
document.getElementById("calendar_new_todo_command").removeAttribute("disabled");
- if (tasksSelected) {
- if (isPropertyValueSame(tasks, "isCompleted")) {
- setBooleanAttribute(document.getElementById("calendar-context-markcompleted"), "checked", tasks[0].isCompleted);
- } else {
- document.getElementById("calendar-context-markcompleted").setAttribute("checked", false);
- }
- }
+ changeMenuForTask(aEvent);
}
/**
- * Handler function to mark selected tasks as completed.
- *
- * XXXberend Please explain more and rename to something easier to understand.
+ * Change the opening menu for the selected tasks.
*
- * @param aEvent The DOM event that triggered this command.
- * @param aProgress The progress percentage to set.
+ * @param aEvent The popupshowing event of the opening menu.
*/
-function contextChangeTaskProgress2(aEvent, aProgress) {
- contextChangeTaskProgress(aEvent, aProgress);
- document.getElementById("calendar_percentComplete-100_command2").checked = false;
+function changeMenuForTask(aEvent) {
+ let tasks = getSelectedTasks(aEvent);
+ let tasksSelected = (tasks.length > 0);
+ if (tasksSelected) {
+ let cmd = document.getElementById("calendar_toggle_completed_command");
+ if (isPropertyValueSame(tasks, "isCompleted")) {
+ setBooleanAttribute(cmd, "checked", tasks[0].isCompleted);
+ } else {
+ setBooleanAttribute(cmd, "checked", false);
+ }
+ }
}
/**
* Handler function to change the progress of all selected tasks.
*
* @param aEvent The DOM event that triggered this command.
* @param aProgress The progress percentage to set.
*/
@@ -281,21 +278,19 @@ function tasksToMail(aEvent) {
* Convert selected tasks to events.
*/
function tasksToEvents(aEvent) {
var tasks = getSelectedTasks(aEvent);
calendarCalendarButtonDNDObserver.onDropItems(tasks);
}
/**
- * Toggle the completed state on tasks retrieved from the given event.
+ * Toggle the completed state on selected tasks.
*
- * XXXberend Please clarify if this is correct and describe more clearly.
- *
- * @param aEvent The command event that holds information about the tasks.
+ * @param aEvent The originating event, can be null.
*/
function toggleCompleted(aEvent) {
if (aEvent.target.getAttribute("checked") == "true") {
- contextChangeTaskProgress(aEvent, 100);
+ contextChangeTaskProgress(aEvent, 0);
} else {
- contextChangeTaskProgress(aEvent, 0);
+ contextChangeTaskProgress(aEvent, 100);
}
}
--- a/calendar/base/content/calendar-task-view.xul
+++ b/calendar/base/content/calendar-task-view.xul
@@ -158,18 +158,18 @@
class="msgHeaderView-button">
<menupopup id="task-actions-category-menupopup"
onpopupshowing="addCategoryNames(event)"/>
</button>
<button id="task-actions-markcompleted"
type="menu-button"
label="&calendar.context.markcompleted.label;"
tooltiptext="&calendar.task.complete.button.tooltip;"
- command="calendar_percentComplete-100_command2"
- observes="calendar_percentComplete-100_command2"
+ command="calendar_toggle_completed_command"
+ observes="calendar_toggle_completed_command"
class="msgHeaderView-button">
<menupopup id="task-actions-markcompleted-menupopup" type="task-progress"/>
</button>
<button id="task-actions-priority"
type="menu"
label="&calendar.context.priority.label;"
tooltiptext="&calendar.task.priority.button.tooltip;"
command="calendar_general-priority_command"
--- a/calendar/base/content/calendar-unifinder.js
+++ b/calendar/base/content/calendar-unifinder.js
@@ -212,17 +212,17 @@ var unifinderObserver = {
var filter = unifinderTreeView.mFilter;
if (filter.startDate && filter.endDate && (aItem.parentItem == aItem)) {
items = aItem.getOccurrencesBetween(filter.startDate, filter.endDate, {});
} else {
items = [aItem];
}
// XXX: do we really still need this, we are always checking it in the refreshInternal
unifinderTreeView.removeItems(items.filter(filter.isItemInFilters, filter));
- },
+ },
observe: function uO_observe(aSubject, aTopic, aPrefName) {
switch (aPrefName) {
case "calendar.date.format":
case "calendar.timezone.local":
refreshEventTree();
break;
}
@@ -438,33 +438,30 @@ function unifinderKeyPress(aEvent) {
break;
}
}
/**
* Tree controller for unifinder search results
*/
var unifinderTreeView = {
-
tree: null,
treeElement: null,
doingSelection: false,
mFilter: null,
-
-
mSelectedColumn: null,
sortDirection: null,
/**
* Returns the currently selected column in the unifinder (used for sorting).
*/
get selectedColumn uTV_getSelectedColumn() {
return this.mSelectedColumn;
},
-
+
/**
* Sets the currently selected column in the unifinder (used for sorting).
*/
set selectedColumn uTV_setSelectedColumn(aCol) {
var tree = document.getElementById("unifinder-search-results-tree");
var treecols = tree.getElementsByTagName("treecol");
for (var i = 0; i < treecols.length; i++) {
var col = treecols[i];
@@ -542,17 +539,17 @@ var unifinderTreeView = {
* that were previously in the unifinder.
*/
setItems: function uTV_setItems(aItemArray, aDontSort) {
var oldCount = this.eventArray.length;
this.eventArray = aItemArray.slice(0);
if (this.tree) {
this.tree.rowCountChanged(0, (this.eventArray.length - oldCount));
}
-
+
if (aDontSort) {
this.calculateIndexMap();
} else {
this.sortItems();
}
},
/**
@@ -777,17 +774,17 @@ var unifinderTreeView = {
getProgressMode: function uTV_getProgressMode(aRow, aCol) {},
getCellValue: function uTV_getCellValue(aRow, aCol) {
return null;
},
getCellText: function uTV_getCellText(row, column) {
- calendarEvent = this.eventArray[row];
+ let calendarEvent = this.eventArray[row];
switch (column.element.getAttribute("itemproperty")) {
case "title":
return calendarEvent.title;
case "startDate":
return formatUnifinderEventDateTime(calendarEvent.startDate);
--- a/calendar/base/content/calendar-view-core.xml
+++ b/calendar/base/content/calendar-view-core.xml
@@ -18,16 +18,17 @@
- The Initial Developer of the Original Code is
- Joey Minta <jminta@gmail.com>
- Portions created by the Initial Developer are Copyright (C) 2005
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- Michael Büttner <michael.buettner@sun.com>
- Philipp Kewisch <mozilla@kewis.ch>
+ - Martin Schroeder <mschroeder@mozilla.x-home.org>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
@@ -43,17 +44,17 @@
<bindings id="calendar-core-view-bindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="calendar-editable-item">
- <content tooltip="itemTooltip">
+ <content mousethrough="never" tooltip="itemTooltip">
<xul:vbox flex="1">
<xul:hbox>
<xul:box anonid="event-container"
class="calendar-color-box"
xbl:inherits="calendar-uri,calendar-id"
flex="1">
<xul:box class="calendar-event-selection" orient="horizontal" flex="1">
<xul:stack anonid="eventbox"
@@ -62,67 +63,76 @@
xbl:inherits="readonly,flashing,alarm,allday,priority,progress,status,calendar,categories">
<xul:hbox class="calendar-event-details">
<xul:vbox align="left"
flex="1"
xbl:inherits="context">
<xul:label anonid="event-name"
crop="end"
flex="1"
- style="margin: 0;"/>
+ style="margin: 0;">
+ <html:div anonid="event-name-div"/>
+ </xul:label>
<xul:textbox anonid="event-name-textbox"
class="plain"
crop="end"
hidden="true"
style="background: transparent !important;"
wrap="true"/>
<xul:spacer flex="1"/>
</xul:vbox>
- <xul:hbox anonid="alarm-icons-box"
- class="alarm-icons-box"
- align="center"
- xbl:inherits="flashing"/>
+ <xul:stack>
+ <xul:calendar-category-box anonid="category-box"
+ xbl:inherits="categories"
+ pack="end"/>
+ <xul:hbox anonid="alarm-icons-box"
+ class="alarm-icons-box"
+ align="center"
+ xbl:inherits="flashing"/>
+ </xul:stack>
</xul:hbox>
<xul:image anonid="gradient"
class="calendar-event-box-gradient"
- height="1px"/>
+ height="1px"
+ mousethrough="always"/>
</xul:stack>
</xul:box>
- <xul:calendar-category-box anonid="category-box" xbl:inherits="categories"/>
</xul:box>
</xul:hbox>
</xul:vbox>
</content>
<implementation>
<constructor><![CDATA[
Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
- var self = this;
- this.eventNameInput.onblur = function onBlur() { self.stopEditing(true); };
- this.eventNameInput.onkeypress = function onKeyPress(event) {
+ let self = this;
+ this.eventNameTextbox.onblur = function onBlur() {
+ self.stopEditing(true);
+ };
+ this.eventNameTextbox.onkeypress = function onKeyPress(event) {
// save on enter
if (event.keyCode == 13)
self.stopEditing(true);
// abort on escape
else if (event.keyCode == 27)
self.stopEditing(false);
};
function stopPropagationIfEditing(event) {
if (self.mEditing) {
event.stopPropagation();
}
}
// while editing, single click positions cursor, so don't propagate.
- this.eventNameInput.onclick = stopPropagationIfEditing;
+ this.eventNameTextbox.onclick = stopPropagationIfEditing;
// while editing, double click selects words, so don't propagate.
- this.eventNameInput.ondblclick = stopPropagationIfEditing;
+ this.eventNameTextbox.ondblclick = stopPropagationIfEditing;
// while editing, don't propagate mousedown/up (selects calEvent).
- this.eventNameInput.onmousedown = stopPropagationIfEditing;
- this.eventNameInput.onmouseup = stopPropagationIfEditing;
+ this.eventNameTextbox.onmousedown = stopPropagationIfEditing;
+ this.eventNameTextbox.onmouseup = stopPropagationIfEditing;
]]></constructor>
<field name="mOccurrence">null</field>
<field name="mSelected">false</field>
<field name="mCalendarView">null</field>
<property name="selected">
<getter><![CDATA[
@@ -160,28 +170,26 @@
return val;
]]></setter>
</property>
<property name="eventNameLabel" readonly="true"
onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'event-name');"/>
<property name="eventNameTextbox" readonly="true"
onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'event-name-textbox');"/>
- <property name="eventNameInput" readonly="true"
- onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'event-name-textbox').inputField;"/>
+
<method name="setEditableLabel">
<body><![CDATA[
- var evl = this.eventNameLabel;
- var item = this.mOccurrence;
-
+ let evldiv = document.getAnonymousElementByAttribute(this, "anonid", "event-name-div");
+ let item = this.mOccurrence;
if (item.title && item.title != "") {
- evl.value = item.title;
+ evldiv.textContent = item.title;
} else {
- evl.value = calGetString("calendar", "eventUntitled")
+ evldiv.textContent = calGetString("calendar", "eventUntitled");
}
]]></body>
</method>
<method name="setCSSClasses">
<body><![CDATA[
var item = this.mOccurrence;
this.setAttribute("calendar-uri", item.calendar.uri.spec);
@@ -231,17 +239,17 @@
if (item.priority > 0 && item.priority < 5) {
this.setAttribute("priority", "high");
} else if (item.priority > 5 && item.priority < 10) {
this.setAttribute("priority", "low");
}
// status attribute
if (item.status) {
- this.setAttribute("status", item.status.toLowerCase());
+ this.setAttribute("status", item.status.toUpperCase());
}
// calendar name
this.setAttribute("calendar", item.calendar.name.toLowerCase());
// Invitation
if (item.calendar instanceof Components.interfaces.calISchedulingSupport &&
item.calendar.isInvitation(item)) {
@@ -259,18 +267,17 @@
this.mOriginalTextLabel = this.mOccurrence.title;
this.eventNameLabel.setAttribute("hidden", "true");
this.mEditing = true;
this.eventNameTextbox.value = this.mOriginalTextLabel;
this.eventNameTextbox.removeAttribute("hidden");
- this.eventNameInput.focus();
- this.eventNameInput.select();
+ this.eventNameTextbox.select();
]]></body>
</method>
<method name="select">
<parameter name="event"/>
<body><![CDATA[
if (!this.calendarView) {
return;
}
@@ -324,17 +331,17 @@
// the 'single click edit' timeout. Otherwise select the item too.
// Also, check if the calendar is readOnly or we are offline.
if (this.selected && isCalendarWritable(this.mOccurrence.calendar)) {
var self = this;
if (this.editingTimer) {
clearTimeout(this.editingTimer);
}
- this.editingTimer = setTimeout(function alldayTimeout() { self.startEditing(); }, 350);
+ this.editingTimer = setTimeout(function editingTimeout() { self.startEditing(); }, 350);
} else {
this.select(event);
event.stopPropagation();
}
]]></handler>
<handler event="dblclick" button="0"><![CDATA[
event.stopPropagation();
--- a/calendar/base/content/calendar-views.js
+++ b/calendar/base/content/calendar-views.js
@@ -341,33 +341,28 @@ function switchToView(aViewType) {
node.setAttribute(attr, node.getAttribute(attr + "-" + aViewType));
} else {
node.setAttribute(attr, node.getAttribute(attr + "-all"));
}
}
// Set up the labels for the context menu
["calendar-view-context-menu-next",
- "calendar-view-context-menu-previous"].forEach(function(x) setupViewNode(x, "label"));
+ "calendar-view-context-menu-previous",
+ "calendar-go-menu-next",
+ "calendar-go-menu-previous"].forEach(function(x) setupViewNode(x, "label"));
+
+ ["calendar-go-menu-next",
+ "calendar-go-menu-previous"].forEach(function(x) setupViewNode(x, "accesskey"));
// Set up the labels for the view navigation
["previous-view-button",
"today-view-button",
"next-view-button"].forEach(function(x) setupViewNode(x, "tooltiptext"));
-
-
- // Disable the menuitem when not in day or week view.
- var rotated = document.getElementById("calendar_toggle_orientation_command");
- if (aViewType == "day" || aViewType == "week") {
- rotated.removeAttribute("disabled");
- } else {
- rotated.setAttribute("disabled", "true");
- }
-
try {
selectedDay = viewDeck.selectedPanel.selectedDay;
currentSelection = viewDeck.selectedPanel.getSelectedItems({});
} catch (ex) {
// This dies if no view has even been chosen this session, but that's
// ok because we'll just use now() below.
}
--- a/calendar/base/content/calendar-views.xml
+++ b/calendar/base/content/calendar-views.xml
@@ -51,16 +51,19 @@
<binding id="calendar-day-view"
extends="chrome://calendar/content/calendar-multiday-view.xml#calendar-multiday-view">
<implementation implements="calICalendarView">
<property name="observerID">
<getter><![CDATA[
return "day-view-observer";
]]></getter>
</property>
+ <property name="supportsWorkdaysOnly"
+ readonly="true"
+ onget="return false;"/>
<!--Public methods-->
<method name="goToDay">
<parameter name="aDate"/>
<body><![CDATA[
aDate = aDate.getInTimezone(this.timezone);
this.setDateRange(aDate, aDate);
this.selectedDay = aDate;
@@ -128,17 +131,17 @@
<field name="mWeeksInView">4</field>
<property name="weeksInView">
<getter><![CDATA[
return this.mWeeksInView;
]]></getter>
<setter><![CDATA[
this.mWeeksInView = val;
- setPref("calendar.weeks.inview", val);
+ setPref("calendar.weeks.inview", Number(val));
this.refreshView();
return val;
]]></setter>
</property>
<property name="observerID">
<getter><![CDATA[
return "multiweek-view-observer";
--- a/calendar/base/content/dialogs/calendar-event-dialog-attendees.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog-attendees.js
@@ -615,18 +615,18 @@ function onResize() {
}
/**
* Handler function to call when changing the calendar used in this dialog.
*
* @param calendar The calendar to change to.
*/
function onChangeCalendar(calendar) {
- var args = window.arguments[0];
- var organizer = args.organizer;
+ let args = window.arguments[0];
+ let organizer = args.organizer;
// set 'mIsReadOnly' if the calendar is read-only
if (calendar && calendar.readOnly) {
gIsReadOnly = true;
}
// assume we're the organizer [in case that the calendar
// does not support the concept of identities].
@@ -637,17 +637,17 @@ function onChangeCalendar(calendar) {
if (gIsReadOnly || gIsInvitation) {
document.getElementById("next-slot")
.setAttribute('disabled', 'true');
document.getElementById("previous-slot")
.setAttribute('disabled', 'true');
}
- var freebusy = document.getElementById("freebusy-grid");
+ let freebusy = document.getElementById("freebusy-grid");
freebusy.onChangeCalendar(calendar);
}
/**
* Updates the slot buttons.
*/
function updateButtons() {
var previousButton = document.getElementById("previous-slot");
@@ -874,17 +874,16 @@ function initTimeRange() {
* (calid) and a flag that tells if the user has entered text before the last
* onModify was called (dirty).
*
* @param event The DOM event that caused the modification.
*/
function onModify(event) {
onResize();
document.getElementById("freebusy-grid").onModify(event);
-
}
/**
* Handler function for the "rowchange" event, emitted from the attendees-list
* binding. event.details is the row that was changed to.
*
* @param event The DOM event caused by the row change.
*/
--- a/calendar/base/content/dialogs/calendar-event-dialog-attendees.xul
+++ b/calendar/base/content/dialogs/calendar-event-dialog-attendees.xul
@@ -16,16 +16,17 @@
-
- The Initial Developer of the Original Code is Sun Microsystems.
- Portions created by the Initial Developer are Copyright (C) 2006
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- Michael Buettner <michael.buettner@sun.com>
- Simon Vaillancourt <simon.at.orcl@gmail.com>
+ - Stefan Sitter <ssitter@gmail.com>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
@@ -122,85 +123,102 @@
flex="1"
id="vertical-scrollbar"
maxpos="100"/>
<box class="attendee-spacer-bottom"/>
</vbox>
</hbox>
<hbox>
<vbox>
- <hbox align="center">
- <box class="legend" status="FREE"/>
- <label value="&event.freebusy.legend.free;"/>
- </hbox>
- <hbox align="center">
- <box class="legend" status="BUSY_TENTATIVE"/>
- <label value="&event.freebusy.legend.busy_tentative;"/>
- </hbox>
- <hbox align="center">
- <box class="legend" status="BUSY"/>
- <label value="&event.freebusy.legend.busy;"/>
- </hbox>
<hbox>
- <button label="&event.freebusy.minus;"
- oncommand="onMinus();"/>
- <button label="&event.freebusy.plus;"
- oncommand="onPlus();"/>
- </hbox>
- <hbox collapsed="true">
- <label value="&event.organizer.label;" disabled="true" control="event-organizer"/>
- <textbox id="event-organizer"
- disabled="true"
- flex="true"/>
- </hbox>
- </vbox>
- <vbox>
- <hbox align="center">
- <box class="legend" status="BUSY_UNAVAILABLE"/>
- <label value="&event.freebusy.legend.busy_unavailable;"/>
+ <grid>
+ <columns>
+ <column/><!-- role icon -->
+ <column/><!-- role description -->
+ <column/><!-- status color -->
+ <column/><!-- status description -->
+ <column/><!-- status color -->
+ <column/><!-- status description -->
+ </columns>
+ <rows>
+ <row align="center">
+ <image class="role-icon" role="REQ-PARTICIPANT"/>
+ <label value="&event.attendee.role.required;"/>
+ <box class="legend" status="FREE"/>
+ <label value="&event.freebusy.legend.free;"/>
+ <box class="legend" status="BUSY_UNAVAILABLE"/>
+ <label value="&event.freebusy.legend.busy_unavailable;"/>
+ </row>
+ <row align="center">
+ <image class="role-icon" role="OPT-PARTICIPANT"/>
+ <label value="&event.attendee.role.optional;"/>
+ <box class="legend" status="BUSY_TENTATIVE"/>
+ <label value="&event.freebusy.legend.busy_tentative;"/>
+ <box class="legend" status="UNKNOWN"/>
+ <label value="&event.freebusy.legend.unknown;"/>
+ </row>
+ <row align="center">
+ <image class="role-icon" role="CHAIR"/>
+ <label value="&event.attendee.role.chair;"/>
+ <box class="legend" status="BUSY"/>
+ <label value="&event.freebusy.legend.busy;"/>
+ </row>
+ </rows>
+ </grid>
</hbox>
<hbox align="center">
- <box class="legend" status="UNKNOWN"/>
- <label value="&event.freebusy.legend.unknown;"/>
- </hbox>
- </vbox>
- <spacer flex="1"/>
- <grid>
- <columns>
- <column/>
- <column flex="1"/>
- </columns>
- <rows>
- <row align="center">
+ <button label="&event.freebusy.minus;" oncommand="onMinus();"/>
+ <button label="&event.freebusy.plus;" oncommand="onPlus();"/>
<spacer/>
- <checkbox id="all-day"
- oncommand="changeAllDay();"
- label="&event.alldayevent.label;"/>
- </row>
- <row align="center">
- <label value="&newevent.from.label;" control="event-starttime"/>
- <datetimepicker id="event-starttime"
- onchange="updateStartTime();"/>
- <label id="timezone-starttime"
- crop="right"
- class="text-link"
- flex="1"
+ <label value="&event.organizer.label;"
collapsed="true"
- hyperlink="true"
- onclick="editStartTimezone()"/>
- </row>
- <row align="center">
- <label value="&newevent.to.label;" control="event-endtime"/>
- <datetimepicker id="event-endtime"
- onchange="updateEndTime();"/>
- <label id="timezone-endtime"
- crop="right"
- class="text-link"
- flex="1"
- collapsed="true"
- hyperlink="true"
- onclick="editEndTimezone()"/>
- </row>
- </rows>
- </grid>
+ disabled="true"
+ control="event-organizer"/>
+ <textbox id="event-organizer"
+ collapsed="true"
+ disabled="true"
+ flex="true"/>
+ </hbox>
+ </vbox>
+ <spacer flex="1"/>
+ <vbox>
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <spacer/>
+ <checkbox id="all-day"
+ oncommand="changeAllDay();"
+ label="&event.alldayevent.label;"/>
+ </row>
+ <row align="center">
+ <label value="&newevent.from.label;" control="event-starttime"/>
+ <datetimepicker id="event-starttime"
+ onchange="updateStartTime();"/>
+ <label id="timezone-starttime"
+ crop="right"
+ class="text-link"
+ flex="1"
+ collapsed="true"
+ hyperlink="true"
+ onclick="editStartTimezone()"/>
+ </row>
+ <row align="center">
+ <label value="&newevent.to.label;" control="event-endtime"/>
+ <datetimepicker id="event-endtime"
+ onchange="updateEndTime();"/>
+ <label id="timezone-endtime"
+ crop="right"
+ class="text-link"
+ flex="1"
+ collapsed="true"
+ hyperlink="true"
+ onclick="editEndTimezone()"/>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
</hbox>
<separator class="groove"/>
</dialog>
--- a/calendar/base/content/dialogs/calendar-event-dialog-recurrence.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog-recurrence.js
@@ -169,17 +169,28 @@ function initializeControls(rule) {
setElementValue("monthly-weekday", startDate.weekday + 1);
} else {
if (byDayRuleComponent.length > 0) {
document.getElementById("monthly-group").selectedIndex = 0;
var ruleInfo = getOrdinalAndWeekdayOfRule(byDayRuleComponent[0]);
setElementValue("monthly-ordinal", ruleInfo.ordinal);
setElementValue("monthly-weekday", ruleInfo.weekday);
} else if (byMonthDayRuleComponent.length > 0) {
- if (byMonthDayRuleComponent.length == 1 && byMonthDayRuleComponent[0] == -1) {
+ if (byMonthDayRuleComponent.length == 31 &&
+ byMonthDayRuleComponent.every(function (element, index, array) {
+ for (let i = 0; i < array.length; i++) {
+ if ((index + 1) == array[i]) {
+ return true;
+ }
+ }
+ return false;
+ })) {
+ setElementValue("monthly-ordinal", 0);
+ setElementValue("monthly-weekday", -1);
+ } else if (byMonthDayRuleComponent.length == 1 && byMonthDayRuleComponent[0] == -1) {
document.getElementById("monthly-group").selectedIndex = 0;
setElementValue("monthly-ordinal", byMonthDayRuleComponent[0]);
setElementValue("monthly-weekday", byMonthDayRuleComponent[0]);
} else {
document.getElementById("monthly-group").selectedIndex = 1;
document.getElementById("monthly-days").days = byMonthDayRuleComponent;
}
}
@@ -289,17 +300,26 @@ function onSave(item) {
recRule.type = "MONTHLY";
var monthInterval = Number(getElementValue("monthly-interval"));
recRule.interval = monthInterval;
var monthlyGroup = document.getElementById("monthly-group");
if (monthlyGroup.selectedIndex==0) {
var ordinal = Number(getElementValue("monthly-ordinal"));
var day_of_week = Number(getElementValue("monthly-weekday"));
if (day_of_week < 0) {
- recRule.setComponent("BYMONTHDAY", 1, [ ordinal ]);
+ if (ordinal == 0) {
+ // monthly rule "every day of the month"
+ let onDays = [];
+ for (let i = 0; i < 31; i++) {
+ onDays[i] = i + 1;
+ }
+ recRule.setComponent("BYMONTHDAY", onDays.length, onDays);
+ } else {
+ recRule.setComponent("BYMONTHDAY", 1, [ ordinal ]);
+ }
} else {
var sign = ordinal < 0 ? -1 : 1;
var onDays = [ (Math.abs(ordinal) * 8 + day_of_week) * sign ];
recRule.setComponent("BYDAY", onDays.length, onDays);
}
} else {
var monthlyDays = document.getElementById("monthly-days").days;
if (monthlyDays.length > 0) {
--- a/calendar/base/content/dialogs/calendar-event-dialog.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog.js
@@ -18,16 +18,17 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Michael Buettner <michael.buettner@sun.com>
* Philipp Kewisch <mozilla@kewis.ch>
* Martin Schroeder <mschroeder@mozilla.x-home.org>
* Fred Jendrzejewski <fred.jen@web.de>
* Daniel Boelzle <daniel.boelzle@sun.com>
+ * Markus Adrario <Mozilla@Adrario.de>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
@@ -50,16 +51,17 @@ var gStartTimezone = null;
var gEndTimezone = null;
var gIsReadOnly = false;
var gUserID = null;
var gOrganizerID = null;
var gPrivacy = null;
var gAttachMap = {};
var gPriority = 0;
var gStatus = "NONE";
+var gConfirmCancel = "true";
var gLastRepeatSelection = 0;
var gIgnoreUpdate = false;
var gShowTimeAs = null;
/**
* Checks if the given calendar supports notifying attendees. The item is needed
* since calendars may support notifications for only some types of items.
*
@@ -158,16 +160,17 @@ function onLoad() {
// new items should have a non-empty title.
if (item.isMutable && (!item.title || item.title.length <= 0)) {
item.title = calGetString("calendar-event-dialog",
isEvent(item) ? "newEvent" : "newTask");
}
window.onAcceptCallback = args.onOk;
+ window.mode = args.mode
// we store the item in the window to be able
// to access this from any location. please note
// that the item is either an occurrence [proxy]
// or the stand-alone item [single occurrence item].
window.calendarItem = item;
// we store the array of attendees in the window.
@@ -176,16 +179,17 @@ function onLoad() {
window.attendees = [];
var attendees = item.getAttendees({});
if (attendees && attendees.length) {
for each (var attendee in attendees) {
window.attendees.push(attendee.clone());
}
}
+ window.organizer = null;
if (item.organizer) {
window.organizer = item.organizer.clone();
} else if (item.getAttendees({}).length > 0) {
// previous versions of calendar may have filled ORGANIZER correctly on overridden instances:
let orgId = item.calendar.getProperty("organizerId");
if (orgId) {
let organizer = cal.createAttendee();
organizer.id = orgId;
@@ -237,31 +241,17 @@ function onAccept() {
* Asks the user if the item should be saved and does so if requested. If the
* user cancels, the window should stay open.
*
* XXX Could possibly be consolidated into onCancel()
*
* @return Returns true if the window should be closed.
*/
function onCommandCancel() {
- // find out if we should bring up the 'do you want to save?' question...
- var newItem = saveItem();
- var oldItem = window.calendarItem.clone();
-
- // we need to guide the description text through the text-field since
- // newlines are getting converted which would indicate changes to the
- // text.
- setElementValue("item-description", oldItem.getProperty("DESCRIPTION"));
- setItemProperty(oldItem,
- "DESCRIPTION",
- getElementValue("item-description"));
- setElementValue("item-description", newItem.getProperty("DESCRIPTION"));
-
- if ((newItem.calendar.id == oldItem.calendar.id) &&
- compareItemContent(newItem, oldItem)) {
+ if (isItemChanged() == false) {
return true;
}
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
var promptTitle = calGetString("calendar",
isEvent(window.calendarItem) ?
@@ -299,21 +289,26 @@ function onCommandCancel() {
}
}
/**
* Handler function to be called when the cancel button is pressed.
*
*/
function onCancel() {
- var result = onCommandCancel();
- if (result == true) {
+ if (gConfirmCancel == "true") {
+ var result = onCommandCancel();
+ if (result == true) {
+ dispose();
+ }
+ return result;
+ } else {
dispose();
+ return true;
}
- return result;
}
/**
* Sets up all dialog controls from the information of the passed item.
*
* @param item The item to parse information out of.
*/
function loadDialog(item) {
@@ -532,17 +527,17 @@ function dateTimeControls2State(aKeepDur
if (gStartTime) {
// jsDate is always in OS timezone, thus we create a calIDateTime
// object from the jsDate representation and simply set the new
// timezone instead of converting.
gStartTime = jsDateToDateTime(
getElementValue(startWidgetId),
(menuItem.getAttribute('checked') == 'true') ? gStartTimezone : kDefaultTimezone);
}
-
+
if (gEndTime) {
if (aKeepDuration) {
gEndTime = gStartTime.clone();
if (gItemDuration) {
gEndTime.addDuration(gItemDuration);
gEndTime = gEndTime.getInTimezone(gEndTimezone);
}
} else {
@@ -1507,17 +1502,17 @@ function attachURL() {
if (promptService.prompt(window,
calGetString("calendar-event-dialog",
"specifyLinkLocation"),
calGetString("calendar-event-dialog",
"enterLinkLocation"),
result,
null,
{ value: 0 })) {
-
+
try {
// If something bogus was entered, makeURL may fail.
var attachment = createAttachment();
attachment.uri = makeURL(result.value);
addAttachment(attachment);
} catch (e) {
// TODO We might want to show a warning instead of just not
// adding the file
@@ -1535,31 +1530,31 @@ function attachFile() {
var files;
try {
const nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
fp.init(window,
calGetString("calendar-event-dialog", "selectAFile"),
nsIFilePicker.modeOpenMultiple);
-
- // Check for the last directory
+
+ // Check for the last directory
var lastDir = lastDirectory();
if (lastDir) {
fp.displayDirectory = lastDir;
}
-
+
// Get the attachment
if (fp.show() == nsIFilePicker.returnOK) {
files = fp.files;
}
} catch (ex) {
- dump("failed to get attachments: " +ex+ "\n");
+ dump("failed to get attachments: " +ex+ "\n");
}
-
+
// Check if something has to be done
if (!files || !files.hasMoreElements()) {
return;
}
// Create the attachment
while (files.hasMoreElements()) {
var file = files.getNext().QueryInterface(Components.interfaces.nsILocalFile);
@@ -1575,17 +1570,17 @@ function attachFile() {
// ... and add the attachment.
var attachment = createAttachment();
attachment.uri = makeURL(uriSpec);
// TODO: set the formattype, but this isn't urgent as we don't have
// a type sensitive dialog to start files.
addAttachment(attachment);
}
- }
+ }
}
/**
* Helper function to remember the last directory chosen when attaching files.
* XXX This function is currently unused, will be needed when we support
* attaching files.
*
* @param aFileUri (optional) If passed, the last directory will be set and
@@ -1595,33 +1590,33 @@ function attachFile() {
*/
function lastDirectory(aFileUri) {
if (aFileUri) {
// Act similar to a setter, save the passed uri.
var uri = makeURL(aFileUri);
var file = uri.QueryInterface(Components.interfaces.nsIFileURL).file;
lastDirectory.mValue = file.parent.QueryInterface(Components.interfaces.nsILocalFile);
}
-
+
// In any case, return the value
return (lastDirectory.mValue !== undefined ? lastDirectory.mValue : null);
}
/**
* Turns an url into a string that can be used in UI.
* - For a file:// url, shows the filename.
* - For a http:// url, removes protocol and trailing slash
*
* @param aUri The uri to parse.
* @return A string that can be used in UI.
*/
function makePrettyName(aUri){
var name = aUri.spec;
if (aUri.schemeIs("file")) {
- name = aUri.spec.split("/").pop();
+ name = aUri.spec.split("/").pop();
} else if (aUri.schemeIs("http")) {
name = aUri.spec.replace(/\/$/, "").replace(/^http:\/\//, "");
}
return name;
}
/**
* Adds the given attachment to dialog controls.
@@ -1644,17 +1639,17 @@ function addAttachment(attachment) {
item.setAttribute("class", "listitem-iconic");
if (attachment.uri.schemeIs("file")) {
item.setAttribute("image", "moz-icon://" + attachment.uri);
} else {
item.setAttribute("image", "moz-icon://dummy.html");
}
// full attachment object is stored here
- item.attachment = attachment;
+ item.attachment = attachment;
// Update the number of rows and save our attachment globally
documentLink.rows = documentLink.getRowCount();
gAttachMap[attachment.uri.spec] = attachment;
updateAttachment();
}
/**
@@ -1685,18 +1680,18 @@ function deleteAllAttachments() {
"removeCalendarsTitle"),
calGetString("calendar-event-dialog",
"removeCalendarsText",
[itemCount]),
{});
}
if (ok) {
- var child;
- var documentLink = document.getElementById("attachment-link");
+ let child;
+ let documentLink = document.getElementById("attachment-link");
while (documentLink.hasChildNodes()) {
child = documentLink.removeChild(documentLink.lastChild);
child.attachment = null;
}
gAttachMap = {};
}
updateAttachment();
}
@@ -1708,17 +1703,17 @@ function deleteAllAttachments() {
function openAttachment() {
// Only one file has to be selected and we don't handle base64 files at all
var documentLink = document.getElementById("attachment-link");
if (documentLink.selectedItems.length == 1) {
var attURI = documentLink.getSelectedItem(0).attachment.uri;
var externalLoader = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
.getService(Components.interfaces.nsIExternalProtocolService);
// TODO There should be a nicer dialog
- externalLoader.loadUrl(attURI);
+ externalLoader.loadUrl(attURI);
}
}
/**
* Handler function to handle pressing keys in the attachment listbox.
*
* @param event The DOM event caused by the key press.
*/
@@ -2161,29 +2156,96 @@ function onCommandSave(aIsClosing) {
// XXX Do we want to disable the dialog or at least the save button until
// the call is complete? This might help when the user tries to save twice
// before the call is complete. In that case, we do need a progress bar and
// the ability to cancel the operation though.
var listener = {
onOperationComplete: function(aCalendar, aStatus, aOpType, aId, aItem) {
if (Components.isSuccessCode(aStatus)) {
- window.calendarItem = aItem;
+ if (window.calendarItem.recurrenceId) {
+ // TODO This workaround needs to be removed in bug 396182
+ // We are editing an occurrence. Make sure that the returned
+ // item is the same occurrence, not its parent item.
+ let occ = aItem.recurrenceInfo
+ .getOccurrenceFor(window.calendarItem.recurrenceId);
+ window.calendarItem = occ;
+ } else {
+ // We are editing the parent item, no workarounds needed
+ window.calendarItem = aItem;
+ }
}
}
};
// Let the caller decide how to handle the modified/added item. Only pass
// the above item if we are not closing, otherwise the listener will be
// missing its window afterwards.
window.onAcceptCallback(item, calendar, originalItem, !aIsClosing && listener);
}
/**
+ * This function is called when the user chooses to delete an Item
+ * from the Event/Task dialog
+ *
+ */
+function onCommandDeleteItem() {
+ // only ask for confirmation, if the User changed anything on a new item or we modify an existing item
+ if (isItemChanged() == true || window.mode != "new") {
+ let promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+ let promptTitle = "";
+ let promptMessage = "";
+
+ if (cal.isEvent(window.calendarItem)) {
+ promptTitle = calGetString("calendar", "deleteEventLabel");
+ promptMessage = calGetString("calendar", "deleteEventMessage");
+ } else if (cal.isToDo(window.calendarItem)) {
+ promptTitle = calGetString("calendar", "deleteTaskLabel");
+ promptMessage = calGetString("calendar", "deleteTaskMessage");
+ }
+
+ let answerDelete = promptService.confirm(
+ null,
+ promptTitle,
+ promptMessage);
+ if (answerDelete == false) {
+ return;
+ }
+ }
+
+ if (window.mode != "new") {
+ let deleteListener = {
+ // when deletion of item is complete, close the dialog
+ onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDetail) {
+ if (aId == window.calendarItem.id && aStatus == 0) {
+ gConfirmCancel = "false";
+ document.documentElement.cancelDialog();
+ }
+ }
+ };
+
+ if (window.calendarItem.parentItem.recurrenceInfo && window.calendarItem.recurrenceId) {
+ // if this is a single occurrence of a recurring item
+ let newItem = window.calendarItem.parentItem.clone();
+ newItem.recurrenceInfo.removeOccurrenceAt(window.calendarItem.recurrenceId);
+
+ window.opener.doTransaction("modify", newItem, newItem.calendar,
+ window.calendarItem.parentItem, deleteListener);
+ } else {
+ window.calendarItem.calendar.deleteItem(window.calendarItem, deleteListener);
+ }
+ } else {
+ gConfirmCancel = "false";
+ document.documentElement.cancelDialog();
+ }
+}
+
+/**
* Handler function to toggle toolbar visibility.
*
* @param aToolbarId The id of the XUL toolbar node to toggle.
* @param aMenuitemId The corresponding menuitem in the view menu.
*/
function onCommandViewToolbar(aToolbarId, aMenuItemId) {
var toolbar = document.getElementById(aToolbarId);
var menuItem = document.getElementById(aMenuItemId);
@@ -2212,17 +2274,17 @@ function onCommandViewToolbar(aToolbarId
* @param aToolboxChanged If true, the toolbox has changed.
*/
function DialogToolboxCustomizeDone(aToolboxChanged) {
var menubar = document.getElementById("event-menubar");
for (var i = 0; i < menubar.childNodes.length; ++i) {
menubar.childNodes[i].removeAttribute("disabled");
}
-
+
// make sure our toolbar buttons have the correct enabled state restored to them...
document.commandDispatcher.updateCommands('itemCommands');
// Enable the toolbar context menu items
document.getElementById("cmd_customize").removeAttribute("disabled");
// Update privacy items to make sure the toolbarbutton's menupopup is set
// correctly
@@ -2238,17 +2300,17 @@ function onCommandCustomize() {
// done after a toolbar has been customized.
var toolbox = document.getElementById("event-toolbox");
toolbox.customizeDone = DialogToolboxCustomizeDone;
var menubar = document.getElementById("event-menubar");
for (var i = 0; i < menubar.childNodes.length; ++i) {
menubar.childNodes[i].setAttribute("disabled", true);
}
-
+
// Disable the toolbar context menu items
document.getElementById("cmd_customize").setAttribute("disabled", "true");
var id = "event-toolbox";
if (isSunbird()) {
window.openDialog("chrome://global/content/customizeToolbar.xul",
"CustomizeToolbar",
"chrome,all,dependent",
@@ -2549,17 +2611,17 @@ function updateTimezone() {
}
element.removeAttribute('disabled');
}
} else {
element.setAttribute('collapsed', 'true');
}
}
}
-
+
updateTimezoneElement(startTimezone,
'timezone-starttime',
gStartTime,
false);
updateTimezoneElement(endTimezone,
'timezone-endtime',
gEndTime,
equalTimezones);
@@ -2659,32 +2721,32 @@ function updateAttendees() {
*/
function updateRepeatDetails() {
// Don't try to show the details text for
// anything but a custom recurrence rule.
var item = window.calendarItem;
var recurrenceInfo = window.recurrenceInfo;
var itemRepeat = document.getElementById("item-repeat");
if (itemRepeat.value == "custom" && recurrenceInfo) {
-
+
// First of all collapse the details text. If we fail to
// create a details string, we simply don't show anything.
// this could happen if the repeat rule is something exotic
// we don't have any strings prepared for.
var repeatDetails = document.getElementById("repeat-details");
repeatDetails.setAttribute("collapsed", "true");
-
+
// Try to create a descriptive string from the rule(s).
var kDefaultTimezone = calendarDefaultTimezone();
var startDate = jsDateToDateTime(getElementValue("event-starttime"), kDefaultTimezone);
var endDate = jsDateToDateTime(getElementValue("event-endtime"), kDefaultTimezone);
var allDay = getElementValue("event-all-day", "checked");
var detailsString = recurrenceRule2String(
recurrenceInfo, startDate, endDate, allDay);
-
+
// Now display the string...
if (detailsString) {
var lines = detailsString.split("\n");
repeatDetails.removeAttribute("collapsed");
while (repeatDetails.childNodes.length > lines.length) {
repeatDetails.removeChild(repeatDetails.lastChild);
}
var numChilds = repeatDetails.childNodes.length;
@@ -2856,16 +2918,41 @@ function sendMailToAttendees(aAttendees)
function updateCapabilities() {
updateAttachment();
updatePriority();
updatePrivacy();
updateReminderDetails();
}
/**
+ * find out if the User already changed values in the Dialog
+ *
+ * @return: true if the values in the Dialog have changed. False otherwise.
+ */
+function isItemChanged() {
+ let newItem = saveItem();
+ let oldItem = window.calendarItem.clone();
+
+ // we need to guide the description text through the text-field since
+ // newlines are getting converted which would indicate changes to the
+ // text.
+ setElementValue("item-description", oldItem.getProperty("DESCRIPTION"));
+ setItemProperty(oldItem,
+ "DESCRIPTION",
+ getElementValue("item-description"));
+ setElementValue("item-description", newItem.getProperty("DESCRIPTION"));
+
+ if ((newItem.calendar.id == oldItem.calendar.id) &&
+ compareItemContent(newItem, oldItem)) {
+ return false;
+ }
+ return true;
+}
+
+/**
* Test if a specific capability is supported
*
* @param aCap The capability from "capabilities.<aCap>.supported"
*/
function capSupported(aCap) {
let calendar = getCurrentCalendar();
return calendar.getProperty("capabilities." + aCap + ".supported") !== false;
}
--- a/calendar/base/content/dialogs/calendar-event-dialog.xul
+++ b/calendar/base/content/dialogs/calendar-event-dialog.xul
@@ -18,16 +18,17 @@
- Portions created by the Initial Developer are Copyright (C) 2006
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- Michael Buettner <michael.buettner@sun.com>
- Philipp Kewisch <mozilla@kewis.ch>
- Stefan Sitter <ssitter@gmail.com>
- Fred Jendrzejewski <fred.jen@web.de>
+ - Markus Adrario <Mozilla@Adrario.de>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
@@ -117,16 +118,19 @@
oncommand="openNewMessage()"/>
<command id="cmd_file_new_card"
oncommand="openNewCardDialog()"/>
<command id="cmd_file_close"
oncommand="cancelDialog()"/>
<command id="cmd_save"
disable-on-readonly="true"
oncommand="onCommandSave()"/>
+ <command id="cmd_item_delete"
+ disable-on-readonly="true"
+ oncommand="onCommandDeleteItem()"/>
<command id="cmd_printSetup"
oncommand="PrintUtils.showPageSetup()"/>
<command id="cmd_print"
disabled="true"
oncommand="calPrint()"/>
<!-- Edit menu -->
<command id="cmd_undo"
@@ -227,16 +231,20 @@
<key id="close-key"
modifiers="accel"
key="W"
command="cmd_file_close"/>
<key id="save-key"
modifiers="accel"
key="S"
command="cmd_save"/>
+ <key id="delete-key"
+ modifiers="accel"
+ key="D"
+ command="cmd_item_delete"/>
<key id="print-key"
modifiers="accel"
key="P"
command="cmd_print"/>
<key id="undo-key"
modifiers="accel"
key="Z"
command="cmd_undo"/>
@@ -307,16 +315,22 @@
</menupopup>
</menu>
<menuseparator id="file-menuseparator1"/>
<menuitem id="file-save-menuitem"
label="&event.menu.file.save.label;"
accesskey="&event.menu.file.save.accesskey;"
key="save-key"
command="cmd_save"/>
+ <menuitem id="item-delete-menuitem"
+ label="&event.menu.item.delete.label;"
+ accesskey="&event.menu.item.delete.accesskey;"
+ key="delete-key"
+ command="cmd_item_delete"
+ disable-on-readonly="true"/>
<menuitem id="file-pagesetup-menuitem"
label="&event.menu.file.page.setup.label;"
accesskey="&event.menu.file.page.setup.accesskey;"
command="cmd_printSetup"
disable-on-readonly="true"/>
<menuitem id="file-print-menuitem"
label="&event.menu.file.print.label;"
accesskey="&event.menu.file.print.accesskey;"
@@ -493,31 +507,35 @@
<menu id="options-status-menu"
label="&newevent.status.label;"
accesskey="&newevent.status.accesskey;"
class="event-only"
disable-on-readonly="true">
<menupopup id="options-status-menupopup">
<menuitem id="options-status-none-menuitem"
label="&newevent.status.none.label;"
+ accesskey="&newevent.status.none.accesskey;"
type="checkbox"
command="cmd_status_none"
disable-on-readonly="true"/>
<menuitem id="options-status-tentative-menuitem"
label="&newevent.status.tentative.label;"
+ accesskey="&newevent.status.tentative.accesskey;"
type="checkbox"
command="cmd_status_tentative"
disable-on-readonly="true"/>
<menuitem id="options-status-confirmed-menuitem"
label="&newevent.status.confirmed.label;"
+ accesskey="&newevent.status.confirmed.accesskey;"
type="checkbox"
command="cmd_status_confirmed"
disable-on-readonly="true"/>
<menuitem id="options-status-canceled-menuitem"
label="&newevent.status.cancelled.label;"
+ accesskey="&newevent.status.cancelled.accesskey;"
type="checkbox"
command="cmd_status_cancelled"
disable-on-readonly="true"/>
</menupopup>
</menu>
<menuseparator id="options-menuseparator2"/>
<menu id="options-freebusy-menu"
label="&event.menu.options.show.time.label;"
@@ -605,23 +623,29 @@
</menupopup>
</toolbarbutton>
<toolbarbutton id="button-url"
mode="dialog"
class="cal-toolbarbutton-2"
label="&event.toolbar.attachments.label;"
command="cmd_attach_url"
disable-on-readonly="true"/>
+ <toolbarbutton id="button-delete"
+ mode="dialog"
+ class="cal-toolbarbutton-2"
+ label="&event.toolbar.delete.label;"
+ command="cmd_item_delete"
+ disable-on-readonly="true"/>
</toolbarpalette>
<toolbar id="event-toolbar"
class="chromeclass-toolbar"
customizable="true"
context="event-dialog-toolbar-context-menu"
- defaultset="button-save,button-attendees,button-privacy,button-url"/>
+ defaultset="button-save,button-attendees,button-privacy,button-url,button-delete"/>
<toolbarset id="custom-toolbars"/>
</toolbox>
<grid id="event-grid"
flex="1"
style="padding-top: 8px; padding-bottom: 10px; -moz-padding-start: 8px; -moz-padding-end: 10px;">
<columns id="event-grid-columns">
--- a/calendar/base/content/dialogs/calendar-invitations-dialog.css
+++ b/calendar/base/content/dialogs/calendar-invitations-dialog.css
@@ -14,16 +14,17 @@
* The Original Code is Sun Microsystems code.
*
* The Initial Developer of the Original Code is Sun Microsystems.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Thomas Benisch <thomas.benisch@sun.com>
+ * Martin Schroeder <mschroeder@mozilla.x-home.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
@@ -31,19 +32,17 @@
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
calendar-invitations-richlistbox {
-moz-binding: url(chrome://calendar/content/calendar-invitations-list.xml#calendar-invitations-richlistbox);
- -moz-user-focus: normal;
}
calendar-invitations-richlistitem {
-moz-binding: url(chrome://calendar/content/calendar-invitations-list.xml#calendar-invitations-richlistitem);
- -moz-user-focus: none;
}
calendar-invitations-richlistitem[selected="true"] {
-moz-user-focus: normal;
}
--- a/calendar/base/content/dialogs/calendar-invitations-list.xml
+++ b/calendar/base/content/dialogs/calendar-invitations-list.xml
@@ -13,18 +13,20 @@
- License.
-
- The Original Code is Sun Microsystems code.
-
- The Initial Developer of the Original Code is Sun Microsystems.
- Portions created by the Initial Developer are Copyright (C) 2006
- the Initial Developer. All Rights Reserved.
-
- - Contributor(s): Thomas Benisch <thomas.benisch@sun.com>
+ - Contributor(s):
+ - Thomas Benisch <thomas.benisch@sun.com>
- Philipp Kewisch <mozilla@kewis.ch>
+ - Martin Schroeder <mschroeder@mozilla.x-home.org>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
@@ -74,22 +76,22 @@
<xul:label anonid="organizer" crop="end"/>
<xul:label anonid="spacer" value="" hidden="true"/>
</xul:vbox>
<xul:vbox>
<xul:button anonid="accept"
class="calendar-invitations-richlistitem-accept-button
calendar-invitations-richlistitem-button"
label="&calendar.invitations.list.accept.button.label;"
- hidden="true" oncommand="accept();"/>
+ oncommand="accept();"/>
<xul:button anonid="decline"
class="calendar-invitations-richlistitem-decline-button
calendar-invitations-richlistitem-button"
label="&calendar.invitations.list.decline.button.label;"
- hidden="true" oncommand="decline();"/>
+ oncommand="decline();"/>
</xul:vbox>
</xul:hbox>
</content>
<implementation>
<!-- fields -->
<field name="mDateFormatter">null</field>
<field name="mCalendarItem">null</field>
@@ -238,31 +240,10 @@
</method>
<method name="decline">
<body><![CDATA[
this.participationStatus = "DECLINED";
]]></body>
</method>
</implementation>
-
- <handlers>
- <handler event="DOMAttrModified"><![CDATA[
- if (event.attrName != "selected" ||
- event.originalTarget.getAttribute("anonid") !=
- "invitations-listitem") {
- return;
- }
- var acceptButton = document.getAnonymousElementByAttribute(
- this, "anonid", "accept");
- var declineButton = document.getAnonymousElementByAttribute(
- this, "anonid", "decline");
- if (event.newValue == "true") {
- acceptButton.removeAttribute("hidden");
- declineButton.removeAttribute("hidden");
- } else if (event.newValue == "false") {
- acceptButton.setAttribute("hidden", "true");
- declineButton.setAttribute("hidden", "true");
- }
- ]]></handler>
- </handlers>
</binding>
</bindings>
--- a/calendar/base/content/import-export.js
+++ b/calendar/base/content/import-export.js
@@ -250,17 +250,17 @@ function saveEventsToFile(calendarEventA
fp.defaultExtension = "ics";
// Get a list of exporters
let contractids = new Array();
let catman = Components.classes["@mozilla.org/categorymanager;1"]
.getService(Components.interfaces.nsICategoryManager);
let catenum = catman.enumerateCategory('cal-exporters');
- currentListLength = 0;
+ let currentListLength = 0;
while (catenum.hasMoreElements()) {
let entry = catenum.getNext();
entry = entry.QueryInterface(Components.interfaces.nsISupportsCString);
let contractid = catman.getCategoryEntry('cal-exporters', entry);
let exporter = Components.classes[contractid]
.getService(Components.interfaces.calIExporter);
let types = exporter.getFileTypes({});
let type;
--- a/calendar/base/content/today-pane.js
+++ b/calendar/base/content/today-pane.js
@@ -263,38 +263,35 @@ var TodayPane = {
updatePeriod: function updatePeriod() {
var date = this.start.clone();
return agendaListbox.refreshPeriodDates(date);
},
/**
* Display a certain section in the minday/minimonth part of the todaypane.
*
- * @param aIndex A numeric value:
- * - 1: Show the miniday
- * - 2: show the minimonth
- * - 3: show none of both
+ * @param aSection The section to display
*/
- displayMiniSection: function displayMiniSection(aIndex) {
- document.getElementById("today-minimonth-box").setVisible(aIndex == 2);
- document.getElementById("mini-day-box").setVisible(aIndex == 1);
- document.getElementById("today-none-box").setVisible(aIndex == 3);
- setBooleanAttribute(document.getElementById("today-Minimonth"), "freebusy", aIndex == 2);
+ displayMiniSection: function displayMiniSection(aSection) {
+ document.getElementById("today-minimonth-box").setVisible(aSection == 'minimonth');
+ document.getElementById("mini-day-box").setVisible(aSection == 'miniday');
+ document.getElementById("today-none-box").setVisible(aSection == 'none');
+ setBooleanAttribute(document.getElementById("today-Minimonth"), "freebusy", aSection == 'minimonth');
},
/**
* Disable or enable the today pane menuitems that have an attribute
* name="minidisplay"
*
* @param disable If true, items will be disabled, otherwise enabled.
*/
disableMenuItems: function disableMenuItems(disable) {
- var menu = document.getElementById("today-pane-menu");
+ let menu = document.getElementById("ltnTodayPaneMenuPopup");
if (menu) {
- setAttributeToChildren(menu.firstChild, "disabled", disable, "name", "minidisplay");
+ setAttributeToChildren(menu, "disabled", disable, "name", "minidisplay");
}
},
/**
* Handler function for the DOMAttrModified event used to observe the
* todaypane-splitter.
*
* @param aEvent The DOM event occurring on attribute modification.
--- a/calendar/base/content/today-pane.xul
+++ b/calendar/base/content/today-pane.xul
@@ -91,32 +91,32 @@
mode="mail,calendar,task"
collapsedinmodes="calendar"
persist="collapsed height collapsedinmodes"
broadcaster="modeBroadcaster">
<modebox id="today-none-box"
mode="mail,calendar,task"
collapsedinmodes="mail,calendar,task"
broadcaster="modeBroadcaster"
- refcontrol="today-pane-displaynone"
+ refcontrol="ltnTodayPaneDisplayNone"
persist="collapsedinmodes"/>
<modehbox id="today-minimonth-box"
pack="center"
class="today-subpane"
mode="mail,calendar,task"
broadcaster="modeBroadcaster"
collapsedinmodes="mail,calendar,task"
- refcontrol="today-pane-displayminimonth"
+ refcontrol="ltnTodayPaneDisplayMinimonth"
persist="collapsedinmodes">
<minimonth id="today-Minimonth" freebusy="true" onchange="TodayPane.setDaywithjsDate(this.value);"/>
</modehbox>
<modebox id="mini-day-box"
mode="mail,calendar,task"
class="today-subpane"
- refcontrol="today-pane-displayminiday"
+ refcontrol="ltnTodayPaneDisplayMiniday"
broadcaster="modeBroadcaster"
collapsedinmodes=""
persist="collapsedinmodes">
<stack flex="1">
<image id="mini-day-image" flex="1"/>
<hbox flex="1">
<deck id="dateContainer" selectedIndex="0">
<hbox pack="center">
--- a/calendar/base/content/widgets/calendar-widgets.xml
+++ b/calendar/base/content/widgets/calendar-widgets.xml
@@ -180,17 +180,18 @@
if (this.hasAttribute("collapsedinmodes")) {
var collapsedModes = this.getAttribute("collapsedinmodes").split(",");
var modeIndex = collapsedModes.indexOf(this.currentMode);
collapsedInMode = (modeIndex > -1);
}
if ((aVisible === true) && (pushModeCollapsedAttribute == false)){
display = (aVisible === true) && (!collapsedInMode);
}
- setBooleanAttribute(this, "collapsed", (!display));
+
+ setBooleanAttribute(this, "collapsed", (!display || !this.isVisibleInMode()));
if (pushModeCollapsedAttribute) {
if (!display) {
if (modeIndex == -1) {
collapsedModes.push(this.currentMode);
if (this.getAttribute("collapsedinmodes") == ",") {
collapsedModes.splice(0,2);
}
}
@@ -208,16 +209,17 @@
document.persist(id, "collapsedinmodes");
}
}
if (notifyRefControl === true) {
if (this.hasAttribute("refcontrol")) {
var command = document.getElementById(this.getAttribute("refcontrol"))
if (command) {
command.setAttribute("checked", display);
+ setBooleanAttribute(command, "disabled", !this.isVisibleInMode());
}
}
}
]]></body>
</method>
<method name="isVisibleInMode">
<parameter name="aMode"/>
--- a/calendar/base/public/calICalendarView.idl
+++ b/calendar/base/public/calICalendarView.idl
@@ -76,17 +76,22 @@ interface calICalendarView : nsISupports
*/
attribute calICalendar displayCalendar;
/**
* the controller for this view
*/
attribute calICalendarViewController controller;
-/**
+ /**
+ * If true, the view supports workdays only
+ */
+ readonly attribute boolean supportsWorkdaysOnly;
+
+ /**
* If this is set to 'true', the view should not display days specified to be
* non-workdays. The implementor is responsible for obtaining what those
* days are on its own.
*/
attribute boolean workdaysOnly;
/**
* Whether or not tasks are to be displayed in the calICalendarView
@@ -94,16 +99,21 @@ interface calICalendarView : nsISupports
attribute boolean tasksInView;
/**
* If set, the view will be rotated (i.e time on top, date at left)
*/
attribute boolean rotated;
/**
+ * If true, the view is rotatable
+ */
+ readonly attribute boolean supportsRotation;
+
+ /**
* Whether or not completed tasks are shown in the calICalendarView
*/
attribute boolean showCompleted;
/**
* Ensure that the given date is visible; the view is free
* to show more dates than the given date (e.g. week view
* would show the entire week).
--- a/calendar/base/src/calAlarmService.js
+++ b/calendar/base/src/calAlarmService.js
@@ -175,17 +175,17 @@ calAlarmService.prototype = {
return doQueryInterface(this, calAlarmService.prototype, aIID, null, this);
},
/**
* nsIObserver
*/
observe: function cAS_observe(aSubject, aTopic, aData) {
// This will also be called on app-startup, but nothing is done yet, to
- // prevent unwanted dialogs etc. See bug 325476 and 413296
+ // prevent unwanted dialogs etc. See bug 325476 and 413296
if (aTopic == "profile-after-change" || aTopic == "wake_notification") {
this.shutdown();
this.startup();
}
if (aTopic == "xpcom-shutdown") {
this.shutdown();
}
},
@@ -299,17 +299,17 @@ calAlarmService.prototype = {
start = now.clone();
start.month -= 1;
} else {
// This is a subsequent search, so we got all the past alarms before
start = this.alarmService.mRangeEnd.clone();
}
let until = now.clone();
until.month += 1;
-
+
// We don't set timers for every future alarm, only those within 6 hours
let end = now.clone();
end.hour += kHoursBetweenUpdates;
this.alarmService.mRangeEnd = end.getInTimezone(UTC());
this.alarmService.findAlarms(getCalendarManager().getCalendars({}),
start, until);
}
@@ -326,18 +326,18 @@ calAlarmService.prototype = {
let notifier = Components.classes["@mozilla.org/embedcomp/appstartup-notifier;1"]
.getService(Components.interfaces.nsIObserver);
notifier.observe(null, "alarm-service-shutdown", null);
if (this.mUpdateTimer) {
this.mUpdateTimer.cancel();
this.mUpdateTimer = null;
}
+
let calmgr = cal.getCalendarManager();
-
calmgr.removeObserver(this.calendarManagerObserver);
for each (let calendarItemMap in this.mTimerMap) {
for each (let alarmMap in calendarItemMap) {
for each (let timer in alarmMap) {
timer.cancel();
}
}
@@ -501,25 +501,27 @@ calAlarmService.prototype = {
}
// If the calendar map is empty, remove it from the timer map
if (this.mTimerMap[aItem.calendar.id].toSource() == "({})") {
delete this.mTimerMap[aItem.calendar.id];
}
}
},
-
+
disposeCalendarTimers: function cAS_removeCalendarTimers(aCalendars) {
for each (let calendar in aCalendars) {
- for each (let itemTimerMap in this.mTimerMap[calendar.id]) {
- for each (let timer in itemTimerMap) {
- timer.cancel();
+ if (calendar.id in this.mTimerMap) {
+ for each (let itemTimerMap in this.mTimerMap[calendar.id]) {
+ for each (let timer in itemTimerMap) {
+ timer.cancel();
+ }
}
+ delete this.mTimerMap[calendar.id]
}
- delete this.mTimerMap[calendar.id]
}
},
findAlarms: function cAS_findAlarms(aCalendars, aStart, aUntil) {
let getListener = {
alarmService: this,
onOperationComplete: function cAS_fA_onOperationComplete(aCalendar,
aStatus,
--- a/calendar/base/src/calCachedCalendar.js
+++ b/calendar/base/src/calCachedCalendar.js
@@ -135,18 +135,17 @@ calCachedCalendar.prototype = {
QueryInterface: function cCC_QueryInterface(aIID) {
if (aIID.equals(Components.interfaces.calISchedulingSupport)) {
// check whether uncached calendar supports it:
if (this.mUncachedCalendar.QueryInterface(aIID)) {
return this;
}
}
return doQueryInterface(this, calCachedCalendar.prototype, aIID,
- [Components.interfaces.calICachedCalendar,
- Components.interfaces.calICalendar,
+ [Components.interfaces.calICalendar,
Components.interfaces.nsISupports]);
},
mCachedCalendar: null,
mCachedObserver: null,
mUncachedCalendar: null,
mObservers: null,
mSuperCalendar: null,
@@ -178,23 +177,23 @@ calCachedCalendar.prototype = {
// afterwards.
this.mCachedCalendar.QueryInterface(Components.interfaces.calICalendarProvider)
.deleteCalendar(this.mCachedCalendar, null);
if (this.supportsChangeLog) {
// start with full sync:
this.mUncachedCalendar.resetLog();
}
} else {
- var calType = getPrefSafe("calendar.cache.type", "storage");
+ let calType = getPrefSafe("calendar.cache.type", "storage");
// While technically, the above deleteCalendar should delete the
// whole calendar, this is nothing more than deleting all events
// todos and properties. Therefore the initialization can be
// skipped.
- cachedCalendar = Components.classes["@mozilla.org/calendar/calendar;1?type=" + calType]
- .createInstance(Components.interfaces.calICalendar);
+ let cachedCalendar = Components.classes["@mozilla.org/calendar/calendar;1?type=" + calType]
+ .createInstance(Components.interfaces.calICalendar);
switch (calType) {
case "memory":
if (this.supportsChangeLog) {
// start with full sync:
this.mUncachedCalendar.resetLog();
}
break;
case "storage":
--- a/calendar/base/src/calCalendarManager.js
+++ b/calendar/base/src/calCalendarManager.js
@@ -416,16 +416,19 @@ calCalendarManager.prototype = {
break;
case "cache.updatetimer":
cal.setPref(getPrefBranchFor(id) + "cache.updateTimer", Number(value));
break;
case "backup-time":
case "uniquenum":
cal.setPref(getPrefBranchFor(id) + name, Number(value));
break;
+ case "name":
+ cal.setLocalizedPref(getPrefBranchFor(id) + name, value);
+ break;
default: // keep as string
cal.setPref(getPrefBranchFor(id) + name, value);
break;
}
}
selectPrefs.reset();
}
@@ -786,31 +789,41 @@ calCalendarManager.prototype = {
}
},
getCalendarPref_: function(calendar, name) {
cal.ASSERT(calendar, "Invalid Calendar!");
cal.ASSERT(calendar.id !== null, "Calendar id needs to be set!");
cal.ASSERT(name && name.length > 0, "Pref Name must be non-empty!");
- return cal.getPrefSafe(getPrefBranchFor(calendar.id) + name, null);
+ let branch = (getPrefBranchFor(calendar.id) + name);
+
+ if ( name === "name" ) {
+ return cal.getLocalizedPref(branch, null);
+ }
+ return cal.getPrefSafe(branch, null);
},
setCalendarPref_: function(calendar, name, value) {
cal.ASSERT(calendar, "Invalid Calendar!");
cal.ASSERT(calendar.id !== null, "Calendar id needs to be set!");
cal.ASSERT(name && name.length > 0, "Pref Name must be non-empty!");
let branch = (getPrefBranchFor(calendar.id) + name);
let prefService = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
// delete before to allow pref-type changes:
prefService.deleteBranch(branch);
- cal.setPref(branch, value);
+
+ if ( name === "name" ) {
+ cal.setLocalizedPref(branch, value);
+ } else {
+ cal.setPref(branch, value);
+ }
},
deleteCalendarPref_: function(calendar, name) {
cal.ASSERT(calendar, "Invalid Calendar!");
cal.ASSERT(calendar.id !== null, "Calendar id needs to be set!");
cal.ASSERT(name && name.length > 0, "Pref Name must be non-empty!");
let prefService = Components.classes["@mozilla.org/preferences-service;1"]
--- a/calendar/base/src/calEvent.js
+++ b/calendar/base/src/calEvent.js
@@ -135,17 +135,17 @@ calEvent.prototype = {
return calcomp.serializeToICS();
},
get icalComponent() {
var icssvc = getIcsService();
var icalcomp = icssvc.createIcalComponent("VEVENT");
this.fillIcalComponentFromBase(icalcomp);
this.mapPropsToICS(icalcomp, this.icsEventPropMap);
-
+
var bagenum = this.propertyEnumerator;
while (bagenum.hasMoreElements()) {
var iprop = bagenum.getNext().
QueryInterface(Components.interfaces.nsIProperty);
try {
if (!this.eventPromotedProps[iprop.name]) {
var icalprop = icssvc.createIcalProperty(iprop.name);
icalprop.value = iprop.value;
@@ -181,17 +181,18 @@ calEvent.prototype = {
this.importUnpromotedProperties(event, this.eventPromotedProps);
// Importing didn't really change anything
this.mDirty = false;
},
isPropertyPromoted: function (name) {
- return (this.eventPromotedProps[name]);
+ // avoid strict undefined property warning
+ return (this.eventPromotedProps[name] || false);
},
set startDate(value) {
this.modify();
// We're about to change the start date of an item which probably
// could break the associated calIRecurrenceInfo. We're calling
// the appropriate method here to adjust the internal structure in
--- a/calendar/base/src/calIcsParser.js
+++ b/calendar/base/src/calIcsParser.js
@@ -143,17 +143,17 @@ function ip_processIcalComponent(rootCom
default:
this.mComponents.push(subComp);
break;
}
if (item) {
// Only try to fix ICS from Sunbird 0.2 (and earlier) if it
// has an EXDATE.
- hasExdate = subComp.getFirstProperty("EXDATE");
+ let hasExdate = subComp.getFirstProperty("EXDATE");
if (isFromOldSunbird && hasExdate) {
item = fixOldSunbirdExceptions(item);
}
let rid = item.recurrenceId;
if (!rid) {
this.mItems.push(item);
if (item.recurrenceInfo) {
--- a/calendar/base/src/calTodo.js
+++ b/calendar/base/src/calTodo.js
@@ -116,17 +116,17 @@ calTodo.prototype = {
this.makeItemBaseImmutable();
},
get isCompleted() {
return this.completedDate != null ||
this.percentComplete == 100 ||
this.status == "COMPLETED";
},
-
+
set isCompleted(v) {
if (v) {
if (!this.completedDate)
this.completedDate = jsDateToDateTime(new Date());
this.status = "COMPLETED";
this.percentComplete = 100;
} else {
this.deleteProperty("COMPLETED");
@@ -210,22 +210,23 @@ calTodo.prototype = {
this.mapPropsFromICS(todo, this.icsEventPropMap);
this.importUnpromotedProperties(todo, this.todoPromotedProps);
// Importing didn't really change anything
this.mDirty = false;
},
isPropertyPromoted: function (name) {
- return (this.todoPromotedProps[name]);
+ // avoid strict undefined property warning
+ return (this.todoPromotedProps[name] || false);
},
set entryDate(value) {
this.modify();
-
+
// We're about to change the start date of an item which probably
// could break the associated calIRecurrenceInfo. We're calling
// the appropriate method here to adjust the internal structure in
// order to free clients from worrying about such details.
if (this.parentItem == this) {
var rec = this.recurrenceInfo;
if (rec) {
rec.onStartDateChange(value,this.entryDate);
index 023150b1d23dc34655cc96788fc6bc6e69e45441..d44fb602621d54a80e5a48869dad02ddeb11d66d
GIT binary patch
literal 969
zc$@*r12+7LP)<h;3K|Lk000e1NJLTq000L7002M;1ONa4d<LbA0009RX+uL$P-t&-
zZ*ypGa3D!TLm+T+Z)Rz1WdHzp+GAi~p5W-><jN$#z)(<BQsf@w7#R^6rNDlkfsKKg
zfq{X6fgv%uxWF+Wz=wf>fgvwHFO>lmFfuTFyT-uCz`($8S=Z6U$(4aLz?xi8RKmc(
zaDah<!6+rQIGKTg;R^!;Lwb2hK?wr`V+;cWgGf?#K?wr`V*vvLgGhErkTU}VV+#WV
z6HiHMMF|4~1A}vZL1j^9dPa$Yp{1pzf@4a4QmR65WpPPrZn1)AUUGg>L4HwUNoooM
z0|V3!1_lNOUYGn51^2|vJOv{IRR$`9h{z}f2n!KD2r&dmE-fm92PFf80D}U90fPfW
z07D`}J;MTqqYQ5t#Ti`~OBq)%-eVGD3SyeTbb(ozIg)uH^BWdRmJXI%ta_}itoPU~
z*`~35V-IKF&7s86#qotRk@EtVBi9ygP3{FeQan?6g?K0N3G+?km*HP5pd+wD&{gn?
zP^!=m;a(9Hk-ehfqF=-&i5rPuk*JWAlRPGsFD)&7LZ(bsUG|n-pS+9wABC-og-Uu#
zZ<W`o6sVf0{!}}t-lY+$sigTq>wxwoom5>bJwd%!`bP{F7}gra7~7i2n=+feG`nEF
z(_)F`1gkphT$?!CKs#4^YX@UT9VazsB^L!(c{c@jWe;^vT`v=F8y|PyV7~<a{DAtv
zi9t()cZFOGeHqRaAs1;C6%kz#Gc$Hi+=B$RMBSvY<c5?rsW;O(GAuGPv*u=B%w^BB
z&#x%hRQRq~yCkc0RoR;g{mPQ6T{VogUUgIJA2#YW)ij@Nm1`?(KiVnXRn&dDSGBLP
z|L#PaNwX*aof<#w@C=Qa{j<K$Ntk<jzWIWsi$oT;F8Q`BbNRiMA*)WWaap@>z2$~&
zn@l!u*=n+F`wpv}`**wUIlC`x|AT`$hkhPuKPG;B#YyW^=guUb{dIoQMeR#RuOwdm
zcYV%Hn_GA9)ZbISf8t@@W3eaupJl!fd3o@4{#*HX7d|w7GX4DO>w@p$KLviB`qTQ)
z@&A7S4FC@``j+us00002VoOIv0RM-N%)bBt010qNS#tmY3ljhU3ljkVnw%H_000Mc
zNliru*#sB{0uZvqFrxqf05(ZPK~yNuWBlL1@R$J(m>5t13j+hge^R*r21qeUvj3>3
rmlBHx%H)4T(1Wy#&_R-b$<1W|i%3BGGO41{00000NkvXXu0mjf#lxEm
--- a/calendar/base/themes/pinstripe/calendar-creation-wizard.css
+++ b/calendar/base/themes/pinstripe/calendar-creation-wizard.css
@@ -32,8 +32,12 @@
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#customize-rows > row {
min-height: 26px;
}
+
+.checkbox-no-label > .checkbox-label-box {
+ display: none;
+}
--- a/calendar/base/themes/pinstripe/calendar-event-dialog.css
+++ b/calendar/base/themes/pinstripe/calendar-event-dialog.css
@@ -14,16 +14,17 @@
* The Original Code is Sun Microsystems code.
*
* The Initial Developer of the Original Code is Sun Microsystems.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Michael Buettner <michael.buettner@sun.com>
+ * Markus Adrario <Mozilla@Adrario.de>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
@@ -143,16 +144,34 @@ toolbar[iconsize="small"] .cal-toolbarbu
}
#button-save:hover {
-moz-image-region: rect(32px 192px 64px 160px);
}
#button-save:hover:active {
}
/*--------------------------------------------------------------------
+ * delete button
+ *-------------------------------------------------------------------*/
+
+#button-delete {
+ -moz-image-region: rect(0px 544px 32px 512px);
+}
+#button-delete[disabled="true"],
+#button-delete[disabled="true"]:hover,
+#button-delete[disabled="true"]:hover:active {
+ -moz-image-region: rect(64px 544px 96px 512px);
+}
+#button-delete:hover {
+ -moz-image-region: rect(32px 544px 64px 512px);
+}
+#button-delete:hover:active {
+}
+
+/*--------------------------------------------------------------------
* print button
*-------------------------------------------------------------------*/
#button-print {
-moz-image-region: rect(0px 608px 32px 576px);
}
#button-print[disabled="true"],
#button-print[disabled="true"]:hover,
--- a/calendar/base/themes/pinstripe/calendar-views.css
+++ b/calendar/base/themes/pinstripe/calendar-views.css
@@ -410,28 +410,36 @@ calendar-month-day-box-item {
padding: 0px 1px;
}
calendar-month-day-box-item[selected="true"] .calendar-event-selection {
color: #000000 !important;
background-color: #ffdb67 !important;
}
+.calendar-color-box {
+ /* This rule should be adopted if the alarm image size is changed */
+ min-height: 13px;
+}
+
+calendar-month-day-box-item[selected="true"] .calendar-color-box {
+ color: #000000;
+}
+
.calendar-month-day-box-item-label {
padding: 0px;
margin: 0px;
}
.calendar-month-day-box-item-label[time="true"] {
-moz-margin-end: 4px;
}
.calendar-month-day-box-item-image {
list-style-image: url("chrome://calendar/skin/day-box-item-image.png");
- margin: 2px;
-moz-margin-end: 4px;
display: none;
}
.calendar-month-day-box-item-image[type="todo"]:not([progress]) {
-moz-image-region: rect(0px 11px 11px 0px);
display: inline;
}
@@ -581,24 +589,34 @@ calendar-event-box[invitation-status="NE
calendar-editable-item[invitation-status="NEEDS-ACTION"],
calendar-month-day-box-item[invitation-status="NEEDS-ACTION"] {
border: 2px dotted black;
opacity: 0.5;
}
calendar-event-box[invitation-status="TENTATIVE"],
calendar-editable-item[invitation-status="TENTATIVE"],
-calendar-month-day-box-item[invitation-status="TENTATIVE"] {
+calendar-month-day-box-item[invitation-status="TENTATIVE"],
+calendar-event-box[status="TENTATIVE"],
+calendar-editable-item[status="TENTATIVE"],
+calendar-month-day-box-item[status="TENTATIVE"] {
opacity: 0.5;
}
calendar-event-box[invitation-status="DECLINED"],
calendar-editable-item[invitation-status="DECLINED"],
-calendar-month-day-box-item[invitation-status="DECLINED"] {
- opacity: 0.3;
+calendar-month-day-box-item[invitation-status="DECLINED"],
+calendar-event-box[status="CANCELLED"],
+calendar-editable-item[status="CANCELLED"],
+calendar-month-day-box-item[status="CANCELLED"] {
+ opacity: 0.5;
+ }
+
+.calendar-event-box-container[status="CANCELLED"] div {
+ text-decoration: line-through;
}
/* Navigation controls for the views */
#calendar-nav-control {
background-color: white;
border: solid ThreeDShadow;
border-width: 1px 1px 0;
}
--- a/calendar/base/themes/pinstripe/dialogs/calendar-invitations-dialog.css
+++ b/calendar/base/themes/pinstripe/dialogs/calendar-invitations-dialog.css
@@ -14,16 +14,17 @@
* The Original Code is Sun Microsystems code.
*
* The Initial Developer of the Original Code is Sun Microsystems.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Thomas Benisch <thomas.benisch@sun.com>
+ * Martin Schroeder <mschroeder@mozilla.x-home.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
@@ -40,31 +41,34 @@
}
.calendar-invitations-updating-icon {
list-style-image: url("chrome://global/skin/icons/loading_16.png");
opacity: 0.5;
}
calendar-invitations-richlistbox {
- background-color: white;
+ background-color: -moz-Field;
+ color: -moz-FieldText;
border: 1px solid #7F9DB9;
}
calendar-invitations-richlistitem {
padding-top: 6px;
padding-bottom: 6px;
-moz-padding-start: 7px;
-moz-padding-end: 7px;
min-height: 25px;
border-bottom: 1px dotted #C0C0C0;
}
calendar-invitations-richlistitem[selected="true"] {
- background-image: url("chrome://mozapps/skin/downloads/downloadSelected.png") !important;
+ background-image: url("chrome://mozapps/skin/extensions/itemEnabledFader.png");
+ background-color: Highlight;
+ color: HighlightText;
border-bottom: 1px dotted #7F9DB9;
}
.calendar-invitations-richlistitem-title {
font-weight: bold;
}
.calendar-invitations-richlistitem-icon[status="NEEDS-ACTION"] {
@@ -79,16 +83,21 @@ calendar-invitations-richlistitem[select
.calendar-invitations-richlistitem-icon[status="DECLINED"] {
list-style-image: url("chrome://calendar/skin/calendar-invitations-dialog-list-images.png");
-moz-image-region: rect(0px 96px 32px 64px);
}
.calendar-invitations-richlistitem-button {
margin-bottom: 10px;
+ visibility: hidden;
+}
+
+calendar-invitations-richlistitem[selected="true"] .calendar-invitations-richlistitem-button {
+ visibility: visible;
}
.calendar-invitations-richlistitem-button .button-icon {
margin-top: 0px;
margin-bottom: 0px;
-moz-margin-start: 0px;
-moz-margin-end: 5px;
}
--- a/calendar/base/themes/winstripe/calendar-creation-wizard.css
+++ b/calendar/base/themes/winstripe/calendar-creation-wizard.css
@@ -32,8 +32,12 @@
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#customize-rows > row {
min-height: 26px;
}
+
+.checkbox-no-label > .checkbox-label-box {
+ display: none;
+}
--- a/calendar/base/themes/winstripe/calendar-event-dialog.css
+++ b/calendar/base/themes/winstripe/calendar-event-dialog.css
@@ -14,16 +14,17 @@
* The Original Code is Sun Microsystems code.
*
* The Initial Developer of the Original Code is Sun Microsystems.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Michael Buettner <michael.buettner@sun.com>
+ * Markus Adrario <Mozilla@Adrario.de>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
@@ -143,16 +144,34 @@ toolbar[iconsize="small"] .cal-toolbarbu
}
#button-save:hover {
-moz-image-region: rect(24px 144px 48px 120px);
}
#button-save:hover:active {
}
/*--------------------------------------------------------------------
+ * delete button
+ *-------------------------------------------------------------------*/
+
+#button-delete {
+ -moz-image-region: rect(0px 408px 24px 384px);
+}
+#button-delete[disabled="true"],
+#button-delete[disabled="true"]:hover,
+#button-delete[disabled="true"]:hover:active {
+ -moz-image-region: rect(48px 408px 72px 384px);
+}
+#button-delete:hover {
+ -moz-image-region: rect(24px 408px 48px 384px);
+}
+#button-delete:hover:active {
+}
+
+/*--------------------------------------------------------------------
* print button
*-------------------------------------------------------------------*/
#button-print {
-moz-image-region: rect(0px 456px 24px 432px);
}
#button-print[disabled="true"],
#button-print[disabled="true"]:hover,
--- a/calendar/base/themes/winstripe/calendar-views.css
+++ b/calendar/base/themes/winstripe/calendar-views.css
@@ -410,28 +410,36 @@ calendar-month-day-box-item {
padding: 0px 1px;
}
calendar-month-day-box-item[selected="true"] .calendar-event-selection {
color: #000000 !important;
background-color: #ffdb67 !important;
}
+.calendar-color-box {
+ /* This rule should be adopted if the alarm image size is changed */
+ min-height: 13px;
+}
+
+calendar-month-day-box-item[selected="true"] .calendar-color-box {
+ color: #000000;
+}
+
.calendar-month-day-box-item-label {
padding: 0px;
margin: 0px;
}
.calendar-month-day-box-item-label[time="true"] {
-moz-margin-end: 4px;
}
.calendar-month-day-box-item-image {
list-style-image: url("chrome://calendar/skin/day-box-item-image.png");
- margin: 2px;
-moz-margin-end: 4px;
display: none;
}
.calendar-month-day-box-item-image[type="todo"] {
-moz-image-region: rect(0px 11px 11px 0px);
display: inline;
}
@@ -581,24 +589,34 @@ calendar-event-box[invitation-status="NE
calendar-editable-item[invitation-status="NEEDS-ACTION"],
calendar-month-day-box-item[invitation-status="NEEDS-ACTION"] {
border: 2px dotted black;
opacity: 0.6;
}
calendar-event-box[invitation-status="TENTATIVE"],
calendar-editable-item[invitation-status="TENTATIVE"],
-calendar-month-day-box-item[invitation-status="TENTATIVE"] {
+calendar-month-day-box-item[invitation-status="TENTATIVE"],
+calendar-event-box[status="TENTATIVE"],
+calendar-editable-item[status="TENTATIVE"],
+calendar-month-day-box-item[status="TENTATIVE"] {
opacity: 0.6;
}
calendar-event-box[invitation-status="DECLINED"],
calendar-editable-item[invitation-status="DECLINED"],
-calendar-month-day-box-item[invitation-status="DECLINED"] {
- opacity: 0.3;
+calendar-month-day-box-item[invitation-status="DECLINED"],
+calendar-event-box[status="CANCELLED"],
+calendar-editable-item[status="CANCELLED"],
+calendar-month-day-box-item[status="CANCELLED"] {
+ opacity: 0.5;
+ }
+
+.calendar-event-box-container[status="CANCELLED"] div {
+ text-decoration: line-through;
}
/* Navigation controls for the views */
#calendar-nav-control {
background-color: white;
border: solid ThreeDShadow;
border-width: 1px 1px 0;
}
--- a/calendar/base/themes/winstripe/dialogs/calendar-invitations-dialog.css
+++ b/calendar/base/themes/winstripe/dialogs/calendar-invitations-dialog.css
@@ -14,16 +14,17 @@
* The Original Code is Sun Microsystems code.
*
* The Initial Developer of the Original Code is Sun Microsystems.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Thomas Benisch <thomas.benisch@sun.com>
+ * Martin Schroeder <mschroeder@mozilla.x-home.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
@@ -40,31 +41,34 @@
}
.calendar-invitations-updating-icon {
list-style-image: url("chrome://global/skin/icons/loading_16.png");
opacity: 0.5;
}
calendar-invitations-richlistbox {
- background-color: white;
+ background-color: -moz-Field;
+ color: -moz-FieldText;
border: 1px solid #7F9DB9;
}
calendar-invitations-richlistitem {
padding-top: 6px;
padding-bottom: 6px;
-moz-padding-start: 7px;
-moz-padding-end: 7px;
min-height: 25px;
border-bottom: 1px dotted #C0C0C0;
}
calendar-invitations-richlistitem[selected="true"] {
- background-image: url("chrome://mozapps/skin/downloads/downloadSelected.png") !important;
+ background-image: url("chrome://mozapps/skin/extensions/itemEnabledFader.png");
+ background-color: Highlight;
+ color: HighlightText;
border-bottom: 1px dotted #7F9DB9;
}
.calendar-invitations-richlistitem-title {
font-weight: bold;
}
.calendar-invitations-richlistitem-icon[status="NEEDS-ACTION"] {
@@ -79,16 +83,21 @@ calendar-invitations-richlistitem[select
.calendar-invitations-richlistitem-icon[status="DECLINED"] {
list-style-image: url("chrome://calendar/skin/calendar-invitations-dialog-list-images.png");
-moz-image-region: rect(0px 96px 32px 64px);
}
.calendar-invitations-richlistitem-button {
margin-bottom: 10px;
+ visibility: hidden;
+}
+
+calendar-invitations-richlistitem[selected="true"] .calendar-invitations-richlistitem-button {
+ visibility: visible;
}
.calendar-invitations-richlistitem-button .button-icon {
margin-top: 0px;
margin-bottom: 0px;
-moz-margin-start: 0px;
-moz-margin-end: 5px;
}
--- a/calendar/lightning/content/imip-bar.js
+++ b/calendar/lightning/content/imip-bar.js
@@ -17,16 +17,17 @@
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Clint Talbert <ctalbert.moz@gmail.com>
* Matthew Willis <lilmatt@mozilla.com>
* Philipp Kewisch <mozilla@kewis.ch>
* Daniel Boelzle <daniel.boelzle@sun.com>
+ * Martin Schroeder <mschroeder@mozilla.x-home.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
@@ -41,18 +42,17 @@ Components.utils.import("resource://cale
Components.utils.import("resource://calendar/modules/calItipUtils.jsm");
/**
* This bar lives inside the message window.
* Its lifetime is the lifetime of the main thunderbird message window.
*/
function ltnGetMsgRecipient() {
- var msgURI = GetLoadedMessage();
- var msgHdr = messenger.msgHdrFromURI(msgURI);
+ let msgHdr = gMessageDisplay.displayedMessage;
if (!msgHdr) {
return null;
}
var identities;
if (msgHdr.accountKey) {
// First, check if the message has an account key. If so, we can use the
// account identities to find the correct recipient
@@ -148,17 +148,17 @@ const ltnOnItipItem = {
// We are only called upon receipt of an invite, so ensure that isSend
// is false.
itipItem.isSend = false;
// XXX Get these from preferences
itipItem.autoResponse = Components.interfaces.calIItipItem.USER;
- let imipMethod = messenger.msgHdrFromURI(GetLoadedMessage()).getStringProperty("imip_method");
+ let imipMethod = gMessageDisplay.displayedMessage.getStringProperty("imip_method");
if (imipMethod && imipMethod.length != 0 && imipMethod.toLowerCase() != "nomethod") {
itipItem.receivedMethod = imipMethod.toUpperCase();
} else { // There is no METHOD in the content-type header (spec violation).
// Fall back to using the one from the itipItem's ICS.
imipMethod = itipItem.receivedMethod;
}
cal.LOG("iTIP method: " + imipMethod);
--- a/calendar/lightning/content/lightning-scripts.inc
+++ b/calendar/lightning/content/lightning-scripts.inc
@@ -39,18 +39,9 @@
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK ***** -->
<script type="application/javascript" src="chrome://lightning/content/lightning-utils.js"/>
<script type="application/javascript" src="chrome://lightning/content/messenger-overlay-sidebar.js"/>
-<script type="application/javascript" src="chrome://lightning/content/messenger-overlay-toolbar.js"/>
<script type="application/javascript" src="chrome://calendar/content/calendar-invitations-manager.js"/>
-<script type="application/javascript">
- var calendarmenulabel = "&lightning.calendar.label;";
- var calendarmenuaccesskey = "&lightning.calendar.accesskey;";
- var messagemenulabel = "&msgMenu.label;";
- var messagemenuaccesskey = "&msgMenu.accesskey;";
- var tasksmenulabel = "&lightning.tasks.label;";
- var tasksmenuaccesskey = "&lightning.tasks.accesskey;";
-</script>
--- a/calendar/lightning/content/lightning.js
+++ b/calendar/lightning/content/lightning.js
@@ -121,16 +121,19 @@ pref("calendar.week.d6saturdaysoff", tru
// start and end work hour for day and week views
pref("calendar.view.daystarthour", 8);
pref("calendar.view.dayendhour", 17);
// number of visible hours for day and week views
pref("calendar.view.visiblehours", 9);
+// If true, mouse scrolling via shift+wheel will be enabled
+pref("calendar.view.mousescroll", true);
+
// Do not set this! If it's not there, then we guess the system timezone
//pref("calendar.timezone.local", "");
// categories settings
// XXX One day we might want to move this to a locale specific file
// and include a list of locale specific default categories
pref("calendar.categories.names", "");
--- a/calendar/lightning/content/messenger-overlay-sidebar.js
+++ b/calendar/lightning/content/messenger-overlay-sidebar.js
@@ -24,16 +24,17 @@
* Dan Mosedale <dmose@mozilla.org>
* Joey Minta <jminta@gmail.com>
* Simon Paquet <bugzilla@babylonsounds.com>
* Stefan Sitter <ssitter@googlemail.com>
* Thomas Benisch <thomas.benisch@sun.com>
* Michael Buettner <michael.buettner@sun.com>
* Philipp Kewisch <mozilla@kewis.ch>
* Berend Cornelius <berend.cornelius@sun.com>
+ * Martin Schroeder <mschroeder@mozilla.x-home.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
@@ -99,17 +100,17 @@ var calendarTabType = {
/* because calendar does some direct menu manipulation, we need to change
* to the mail mode to clean up after those hacks.
*/
saveTabState: function(aTab) {
ltnSwitch2Mail();
},
};
-window.addEventListener("load", function(e) {
+window.addEventListener("load", function(e) {
document.getElementById('tabmail').registerTabType(calendarTabType); }, false);
function ltnOnLoad(event) {
// nuke the onload, or we get called every time there's
// any load that occurs
document.removeEventListener("load", ltnOnLoad, true);
@@ -125,19 +126,20 @@ function ltnOnLoad(event) {
// Add an unload function to the window so we don't leak any listeners
window.addEventListener("unload", ltnFinish, false);
// Set up invitations manager
scheduleInvitationsUpdate(FIRST_DELAY_STARTUP);
getCalendarManager().addObserver(gInvitationsCalendarManagerObserver);
- var filter = document.getElementById("task-tree-filtergroup");
+ let filter = document.getElementById("task-tree-filtergroup");
filter.value = filter.value || "all";
document.getElementById("modeBroadcaster").setAttribute("mode", gCurrentMode);
+ document.getElementById("modeBroadcaster").setAttribute("checked", "true");
let mailContextPopup = document.getElementById("mailContext");
if (mailContextPopup)
mailContextPopup.addEventListener("popupshowing",
gCalSetupMailContext.popup, false);
}
@@ -259,290 +261,16 @@ function findMailSearchBox() {
return tb2Box;
}
// In later versions, it's possible that a user removed the search box from
// the toolbar.
return null;
}
-var calendarpopuplist = new Array();
-var taskpopuplist = new Array();
-var mailpopuplist = new Array();
-var menulist = new Array();
-#ifdef XP_MACOSX
- var quitMenu = null;
- var prefMenu = null;
-#endif
-
-function ltnInitializeMenus(){
-#ifdef XP_MACOSX
- // The following Mac specific code-lines became necessary due to bug 409845
- prefMenu = document.getElementById("menu_preferences");
- prefMenu.setAttribute("mode", "system");
- quitMenu = document.getElementById("menu_FileQuitItem");
- quitMenu.setAttribute("mode", "system");
-#endif
- copyPopupMenus();
- ltnRemoveMailOnlyItems(calendarpopuplist, "calendar");
- ltnRemoveMailOnlyItems(taskpopuplist, "task");
- document.getElementById("modeBroadcaster").setAttribute("checked", true);
-}
-
-function getMenuElementById(aElementId, aMenuPopup) {
- var element = null;
- var elements = aMenuPopup.getElementsByAttribute("id", aElementId);
- if (elements.length > 0) {
- element = elements[0];
- }
- return element;
- }
-
-/** removes all succeedingmenu elements of a container up to the next
-* menuseparator that thus denotes the end of the section. Elements with the
-* attribute mode == 'calendar' are ignored
-*/
-function removeMenuElementsInSection(aElement, aExcludeMode) {
- var element = aElement;
- if (element) {
- var bleaveloop = false;
- while (!bleaveloop) {
- var ignore = false;
- bleaveloop = element.localName == "menuseparator";
- if (bleaveloop) {
- // we delete the menuseparator only if it's the last element
- // within its container
- bleaveloop = (element.nextSibling != null);
- }
- if (element.hasAttribute("mode")) {
- ignore = element.getAttribute("mode") == aExcludeMode ||
- element.getAttribute("mode") == "calendar,task";
- }
- var nextMenuElement = element.nextSibling;
- if (!ignore) {
- try {
- element.parentNode.removeChild(element);
- } catch (e) {
- dump("Element '" + element.getAttribute("id") + "' could not be removed\n");
- }
- }
- if (!bleaveloop) {
- element = nextMenuElement;
- bleaveloop = (element == null);
- }
- }
- }
-}
-
-function removeElements(aElementList) {
- aElementList.forEach(function(element) {
- try {
- if (element) {
- element.parentNode.removeChild(element);
- }
- } catch (e) {
- dump("Element '" + element.getAttribute("id") + "' could not be removed\n");
- }
- });
-}
-
-function addToPopupList(aMenuElement, aNewPopupMenu, aPopupList, aExcludedModes, aClone, aRemovePopupShowing) {
- var child = aMenuElement.firstChild;
- if (child) {
- if (child.localName == "menupopup") {
- if (aNewPopupMenu) {
- var newPopupMenu = aNewPopupMenu;
- } else {
- var newPopupMenu = child;
- }
- if (aClone) {
- newPopupMenu = newPopupMenu.cloneNode(true);
- if (aRemovePopupShowing) {
- newPopupMenu.removeAttribute("onpopupshowing");
- }
- }
- removeMenuElements(newPopupMenu, aExcludedModes);
- aPopupList.push(newPopupMenu);
- }
- }
-}
-
-function copyPopupMenus() {
- // define menuList...
- menulist.push(document.getElementById("menu_File"));
- menulist.push(document.getElementById("menu_Edit"));
- var menuView = document.getElementById("menu_View");
- menulist.push(menuView);
- menulist.push(menuView.nextSibling); // id-less menu_Go
- menulist.push(document.getElementById("messageMenu"));
- menulist.push(document.getElementById("tasksMenu"));
-
- // define PopupMenus for calendar mode...
- var excludeList = new Array("mail", "task", "system");
- addToPopupList(menulist[0], null, calendarpopuplist, excludeList, true, true);
- addToPopupList(menulist[1], null, calendarpopuplist, excludeList, true, false);
- addToPopupList(menulist[2], null, calendarpopuplist, excludeList, true, true);
- addToPopupList(menulist[3], document.getElementById("calendar-GoPopupMenu"), calendarpopuplist, excludeList, true, false);
- addToPopupList(menulist[4], document.getElementById("calendarCalendarPopupMenu"), calendarpopuplist, excludeList, true, false);
- addToPopupList(menulist[5], null, calendarpopuplist, excludeList, true, false);
-
- // define PopupMenus for task mode...
- var excludeList = new Array("mail", "calendar", "system");
- addToPopupList(menulist[0], null, taskpopuplist, excludeList, true, true);
- addToPopupList(menulist[1], null, taskpopuplist, excludeList, true, false);
- addToPopupList(menulist[2], null, taskpopuplist, excludeList, true, true);
- addToPopupList(menulist[3], document.getElementById("calendar-GoPopupMenu"), taskpopuplist, excludeList, true, false);
- var tasksViewMenuPopup = clonePopupMenu("taskitem-context-menu", "taskitem-menu", "menu-");
- tasksViewMenuPopup.removeChild(getMenuElementById("menu-" + "task-context-menu-modify", tasksViewMenuPopup));
- tasksViewMenuPopup.removeChild(getMenuElementById("menu-" + "task-context-menu-delete", tasksViewMenuPopup));
- addToPopupList(menulist[4], tasksViewMenuPopup, taskpopuplist, excludeList, false, false);
- addToPopupList(menulist[5], null, taskpopuplist, excludeList, true, true);
-
- // define PopupMenus for mail mode...
- var excludeList = new Array("calendar", "task", "calendar,task");
- addToPopupList(menulist[0], null, mailpopuplist, excludeList, false, false);
- addToPopupList(menulist[1], null, mailpopuplist, excludeList, false, false);
- addToPopupList(menulist[2], null, mailpopuplist, excludeList, false, false);
- // copy calendar-GoPopupMenu into Thunderbird's GoPopupMenu to switch modes
- var tbGoPopupMenu = menulist[3].lastChild;
- var calGoPopupMenu = document.getElementById("calendar-GoPopupMenu").cloneNode(true);
- var calGoItem;
- while ((calGoItem = calGoPopupMenu.firstChild)) {
- tbGoPopupMenu.appendChild(calGoPopupMenu.removeChild(calGoItem));
- }
- addToPopupList(menulist[3], null, mailpopuplist, excludeList, false, false);
- addToPopupList(menulist[4], null, mailpopuplist, excludeList, false, false);
- addToPopupList(menulist[5], null, mailpopuplist, excludeList, false, false);
-}
-
-function removeNeedlessSeparators(aMenuPopupList) {
- aMenuPopupList.forEach(function(aMenuPopup) {
- var child = aMenuPopup.firstChild;
- if (child) {
- if (child.localName == "menuseparator") {
- try {
- aMenuPopup.removeChild(child)
- } catch (e) {
- dump("Element '" + child.getAttribute("id") + "' could not be removed\n");
- }
- }
- }
- child = aMenuPopup.lastChild;
- if (child) {
- if (child.localName == "menuseparator") {
- try {
- aMenuPopup.removeChild(child)
- } catch (e) {
- dump("Element '" + child.getAttribute("id") + "' could not be removed\n");
- }
- }
- }
- });
-}
-
-function ltnRemoveMailOnlyItems(aMenuPopupList, aExcludeMode) {
- removeElements(
-// "File" - menu
- [getMenuElementById("openMessageFileMenuitem", aMenuPopupList[0]),
- getMenuElementById("newAccountMenuItem", aMenuPopupList[0]),
- getMenuElementById("fileAttachmentMenu", aMenuPopupList[0]),
- getAdjacentSibling(getMenuElementById("menu_saveAs", aMenuPopupList[0]), 2),
-
-// "Edit" - menu
- getMenuElementById("menu_find", aMenuPopupList[1]),
- getMenuElementById("menu_favoriteFolder", aMenuPopupList[1]),
- getMenuElementById("menu_properties", aMenuPopupList[1]),
- getMenuElementById("menu_accountmgr", aMenuPopupList[1]),
-
-// "View"-menu
- getMenuElementById("menu_showMessengerToolbar", aMenuPopupList[2]),
-
-// "Tools"-menu
- getMenuElementById("tasksMenuMail", aMenuPopupList[5]),
- getMenuElementById("menu_import", aMenuPopupList[5])]);
-
- removeNeedlessSeparators(aMenuPopupList);
-
-// "File" - menu
- [getMenuElementById("menu_newFolder", aMenuPopupList[0]),
- getMenuElementById("menu_saveAs", aMenuPopupList[0]),
- getMenuElementById("menu_getnextnmsg", aMenuPopupList[0]),
- getMenuElementById("menu_renameFolder", aMenuPopupList[0]),
-// getMenuElementById("offlineMenuItem", aMenuPopupList[0]),
-
-// "Edit" - menu
- getMenuElementById("menu_delete", aMenuPopupList[1]),
- getMenuElementById("menu_select", aMenuPopupList[1]),
-
-// "View"-menu
- getMenuElementById("menu_MessagePaneLayout", aMenuPopupList[2]),
- getMenuElementById("viewSortMenu", aMenuPopupList[2]),
- getMenuElementById("viewheadersmenu", aMenuPopupList[2]),
- getMenuElementById("viewTextSizeMenu", aMenuPopupList[2]),
- getMenuElementById("pageSourceMenuItem", aMenuPopupList[2]),
-
-// "Tools"-menu
- getMenuElementById("filtersCmd", aMenuPopupList[5]),
- getMenuElementById("runJunkControls", aMenuPopupList[5])].forEach(function(element){
- removeMenuElementsInSection(element, aExcludeMode);
- });
-}
-
-function swapPopupMenus() {
- var showStatusbar = document.getElementById("menu_showTaskbar").getAttribute("checked");
- var newmenupopuplist = null;
- if (gCurrentMode == "mail") {
- newmenupopuplist = mailpopuplist;
- } else if (gCurrentMode == "calendar") {
- newmenupopuplist = calendarpopuplist;
- } else if (gCurrentMode == "task") {
- newmenupopuplist = taskpopuplist;
- }
- for (var i = 0; i < menulist.length; i++) {
- var menu = menulist[i];
- var oldmenupopup = menu.firstChild;
- if (oldmenupopup) {
- menu.replaceChild(newmenupopuplist[i], oldmenupopup);
- }
- }
-#ifdef XP_MACOSX
- document.getElementById("menu_File").firstChild.appendChild(quitMenu);
- document.getElementById("tasksMenu").firstChild.appendChild(prefMenu);
-#endif
- document.getElementById("menu_showTaskbar").setAttribute("checked", showStatusbar);
- var messageMenu = document.getElementById("messageMenu");
- if (gCurrentMode == "mail") {
- messageMenu.setAttribute("label", messagemenulabel);
- messageMenu.setAttribute("accesskey", messagemenuaccesskey);
- } else if (gCurrentMode == "calendar"){
- messageMenu.setAttribute("label", calendarmenulabel);
- messageMenu.setAttribute("accesskey", calendarmenuaccesskey);
- } else if (gCurrentMode == "task"){
- messageMenu.setAttribute("label", tasksmenulabel);
- messageMenu.setAttribute("accesskey", tasksmenuaccesskey);
- }
-}
-
-function removeMenuElements(aRoot, aModeValue) {
- for (var n = 0; n < aModeValue.length; n++) {
- var modeElements = aRoot.getElementsByAttribute("mode", aModeValue[n]);
- if (modeElements.length > 0) {
- for (var i = modeElements.length-1; i >=0; i--) {
- var element = modeElements[i];
- if (element) {
- var localName = element.localName;
- if (localName =="menuitem" || localName == "menuseparator" || localName == "menu"){
- element.parentNode.removeChild(element);
- }
- }
- }
- }
- }
-}
-
// == invitations link
const FIRST_DELAY_STARTUP = 100;
const FIRST_DELAY_RESCHEDULE = 100;
const FIRST_DELAY_REGISTER = 10000;
const FIRST_DELAY_UNREGISTER = 0;
var gInvitationsOperationListener = {
mCount: 0,
@@ -606,9 +334,117 @@ function openInvitationsDialog() {
gInvitationsCalendarManagerObserver.mCount = 0;
getInvitationsManager().openInvitationsDialog(
gInvitationsOperationListener,
function oiD_callback() {
scheduleInvitationsUpdate(FIRST_DELAY_RESCHEDULE);
});
}
+/**
+ * the current mode is set to a string defining the current
+ * mode we're in. allowed values are:
+ * - 'mode'
+ * - 'mail'
+ * - 'calendar'
+ * - 'task'
+ */
+var gCurrentMode = 'mail';
+
+/**
+ * ltnSwitch2Mail() switches to the mail mode
+ */
+
+function ltnSwitch2Mail() {
+ if (gCurrentMode != 'mail') {
+ var switch2mail = document.getElementById("switch2mail");
+ var switch2calendar = document.getElementById("switch2calendar");
+ var switch2task = document.getElementById("switch2task");
+ switch2mail.setAttribute("checked", "true");
+ switch2calendar.removeAttribute("checked");
+ switch2task.removeAttribute("checked");
+
+ gCurrentMode = 'mail';
+ document.getElementById("modeBroadcaster").setAttribute("mode", gCurrentMode);
+
+ document.commandDispatcher.updateCommands('calendar_commands');
+ window.setCursor("auto");
+ }
+}
+
+/**
+ * ltnSwitch2Calendar() switches to the calendar mode
+ */
+
+function ltnSwitch2Calendar() {
+ if (gCurrentMode != 'calendar') {
+ var switch2mail = document.getElementById("switch2mail");
+ var switch2calendar = document.getElementById("switch2calendar");
+ var switch2task = document.getElementById("switch2task");
+ switch2mail.removeAttribute("checked");
+ switch2calendar.setAttribute("checked", "true");
+ switch2task.removeAttribute("checked");
+
+ gCurrentMode = 'calendar';
+ document.getElementById("modeBroadcaster").setAttribute("mode", gCurrentMode);
+
+ // display the calendar panel on the display deck
+ var viewBox = document.getElementById("calendar-view-box");
+ uncollapseElement(viewBox);
+ var deck = document.getElementById("calendarDisplayDeck");
+ deck.selectedPanel = viewBox;
+
+ // show the last displayed type of calendar view
+ showCalendarView(gLastShownCalendarView);
+
+ document.commandDispatcher.updateCommands('calendar_commands');
+ window.setCursor("auto");
+ }
+}
+
+/**
+ * ltnSwitch2Task() switches to the task mode
+ */
+
+function ltnSwitch2Task() {
+ if (gCurrentMode != 'task') {
+ var switch2mail = document.getElementById("switch2mail");
+ var switch2calendar = document.getElementById("switch2calendar");
+ var switch2task = document.getElementById("switch2task");
+ switch2mail.removeAttribute("checked");
+ switch2calendar.removeAttribute("checked");
+ switch2task.setAttribute("checked", "true");
+
+ gCurrentMode = 'task';
+ document.getElementById("modeBroadcaster").setAttribute("mode", gCurrentMode);
+
+ // display the task panel on the display deck
+ var taskBox = document.getElementById("calendar-task-box");
+ uncollapseElement(taskBox);
+ var deck = document.getElementById("calendarDisplayDeck");
+ deck.selectedPanel = taskBox;
+
+ document.commandDispatcher.updateCommands('calendar_commands');
+ window.setCursor("auto");
+ }
+}
+
+const gCalSetupMailContext = {
+ popup: function gCalSetupMailContext_popup() {
+ let hasSelection = (gFolderDisplay.selectedMessage != null);
+ // Disable the convert menu altogether.
+ setElementValue("mailContext-calendar-convert-menu",
+ !hasSelection && "true", "hidden");
+ }
+};
+
+// Overwrite the InitMessageMenu function, since we never know in which order
+// the popupshowing event will be processed. This function takes care of
+// disabling the message menu when in calendar or task mode.
+function calInitMessageMenu() {
+ calInitMessageMenu.origFunc();
+
+ document.getElementById("markMenu").disabled = (gCurrentMode != 'mail');
+}
+calInitMessageMenu.origFunc = InitMessageMenu;
+InitMessageMenu = calInitMessageMenu;
+
document.addEventListener("load", ltnOnLoad, true);
--- a/calendar/lightning/content/messenger-overlay-sidebar.xul
+++ b/calendar/lightning/content/messenger-overlay-sidebar.xul
@@ -52,17 +52,16 @@
<!ENTITY % dtd3 SYSTEM "chrome://calendar/locale/calendar.dtd" > %dtd3;
<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd"> %messengerDTD;
]>
<?xml-stylesheet href="chrome://lightning/skin/lightning.css" type="text/css"?>
<?xml-stylesheet href="chrome://calendar/content/calendar-view-bindings.css" type="text/css"?>
<?xml-stylesheet href="chrome://calendar/skin/calendar-views.css" type="text/css"?>
-<?xml-stylesheet href="chrome://global/skin/menu.css" type="text/css"?>
<?xml-stylesheet href="chrome://calendar/content/calendar-bindings.css" type="text/css"?>
<?xml-stylesheet href="chrome://calendar/skin/widgets/minimonth.css" type="text/css"?>
<?xml-stylesheet href="chrome://calendar/content/widgets/calendar-widget-bindings.css" type="text/css"?>
<?xml-stylesheet href="chrome://calendar/content/datetimepickers/datetimepickers.css" type="text/css"?>
<?xml-stylesheet href="chrome://calendar/skin/today-pane.css" type="text/css"?>
<?xul-overlay href="chrome://calendar/content/calendar-calendars-list.xul"?>
<?xul-overlay href="chrome://calendar/content/calendar-common-sets.xul"?>
@@ -71,384 +70,425 @@
<overlay id="ltnSidebarOverlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<!-- All JS files that messenger-overlay-sidebar.xul wishes to include *must* go into the
calendar-scripts.inc file (all scripts shared with Sunbird) or lightning-scripts.inc
file (scripts relevant for Lightning-only). -->
#include ../../base/content/calendar-scripts.inc
#include lightning-scripts.inc
-<menupopup id="menu_FilePopup">
- <menu id="menu_Open"
- mode="calendar"
- label="&lightning.menupopup.open.label;"
- accesskey="&lightning.menupopup.open.accesskey;"
- insertafter="menu_New">
- <menupopup id="menu_OpenPopup">
- <menuitem id="ltnOpenMessageFileMenuitem"
- mode="calendar"
- label="&lightning.menupopup.open.message.label;"
- accesskey="&lightning.menupopup.open.message.accesskey;"
- oncommand="MsgOpenFromFile();"/>
- <menuitem id="ltnOpenCalendarFileMenuitem"
- mode="calendar"
- label="&lightning.menupopup.open.calendar.label;"
- accesskey="&lightning.menupopup.open.calendar.accesskey;"
- oncommand="openLocalCalendar();"/>
- </menupopup>
- </menu>
- <menuitem id="calendar-export-selection-menu"
- mode="calendar"
- label="&calendar.export.selection.label;"
- accesskey="&calendar.export.selection.accesskey;"
- command="calendar_export_selection_command"
- observes="calendar_export_selection_command"
- insertafter="menu_close"/>
- <menuitem id="calendar-export-menu"
- mode="calendar"
- label="&calendar.export.calendar;"
- accesskey="&calendar.export.calendar.accesskey;"
- command="calendar_export_command"
- observes="calendar_export_command"
- insertafter="menu_close"/>
- <menuitem id="calendar-import-menu"
- label="&calendar.importcalendar.label;"
- accesskey="&calendar.import.accesskey;"
- command="calendar_import_command"
- observes="calendar_import_command"
- mode="calendar"
- insertafter="menu_close"/>
- <menuseparator id="afterMenu_close"
- mode="calendar"
- insertafter="menu_close"/>
-</menupopup>
+ <menupopup id="menu_FilePopup">
+ <menu id="menu_Open"
+ mode="calendar"
+ label="&lightning.menupopup.open.label;"
+ accesskey="&lightning.menupopup.open.accesskey;"
+ insertafter="menu_New">
+ <menupopup id="menu_OpenPopup">
+ <menuitem id="ltnOpenMessageFileMenuitem"
+ label="&lightning.menupopup.open.message.label;"
+ accesskey="&lightning.menupopup.open.message.accesskey;"
+ oncommand="MsgOpenFromFile();"/>
+ <menuitem id="ltnOpenCalendarFileMenuitem"
+ label="&lightning.menupopup.open.calendar.label;"
+ accesskey="&lightning.menupopup.open.calendar.accesskey;"
+ oncommand="openLocalCalendar();"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ <menuitem id="openMessageFileMenuitem" hidden="true"/>
<menupopup id="menu_NewPopup">
- <menuitem id="ltnNewEvent" label="&lightning.menupopup.new.event.label;"
- accesskey="&event.new.event.accesskey;"
- key="calendar-new-event-key"
- command="calendar_new_event_command"
- observes="calendar_new_event_command"
- position="2"/>
- <menuitem id="ltnNewTask" label="&lightning.menupopup.new.task.label;"
- accesskey="&event.new.todo.accesskey;"
- key="calendar-new-todo-key"
- command="calendar_new_todo_command"
- observes="calendar_new_todo_command"
- position="3"/>
- <menuseparator id="afterltnNewTask" position="4"/>
+ <menuitem id="ltnNewEvent"
+ label="&lightning.menupopup.new.event.label;"
+ insertbefore="menu_newFolder"
+ accesskey="&lightning.menupopup.new.event.accesskey;"
+ key="calendar-new-event-key"
+ command="calendar_new_event_command"
+ observes="calendar_new_event_command"/>
+ <menuitem id="ltnNewTask"
+ label="&lightning.menupopup.new.task.label;"
+ insertbefore="menu_newFolder"
+ accesskey="&lightning.menupopup.new.task.accesskey;"
+ key="calendar-new-todo-key"
+ command="calendar_new_todo_command"
+ observes="calendar_new_todo_command"/>
+ <menuseparator id="afterltnNewTask"
+ insertbefore="menu_newFolder"/>
+
<menuseparator id="beforenewAccountMenuItem"
- mode="mail"
insertbefore="newAccountMenuItem"/>
<menuitem id="ltnNewCalendar" label="&lightning.menupopup.new.calendar.label;"
command="calendar_new_calendar_command"
observes="calendar_new_calendar_command"
- accesskey="&calendar.new.server.accesskey;"
+ accesskey="&lightning.menupopup.new.calendar.accesskey;"
insertafter="newAccountMenuItem"/>
</menupopup>
+
+ <menupopup id="menu_EditPopup">
+ <menuitem id="ltnCalendarProperties"
+ insertafter="menu_properties"
+ label="&calendar.properties.label;"
+ accesskey="&calendar.properties.accesskey;"
+ command="calendar_edit_calendar_command"
+ observes="calendar_edit_calendar_command"/>
+ </menupopup>
+
<menupopup id="menu_View_Popup">
- <menu label="&showCurrentView.label;"
- mode="calendar,task"
- accesskey="&showCurrentView.accesskey;">
- <menupopup>
- <menuitem type="checkbox"
- id="ltn-workdays-only-menuitem"
- label="&calendar.onlyworkday.checkbox.label;"
- accesskey="&calendar.onlyworkday.checkbox.accesskey;"
- mode="calendar"
- observes="calendar_toggle_workdays_only_command"/>
- <menuitem type="checkbox"
- id="ltn-tasks-in-view-menuitem"
- label="&calendar.displaytodos.checkbox.label;"
- accesskey="&calendar.displaytodos.checkbox.accesskey;"
- mode="calendar"
- observes="calendar_toggle_tasks_in_view_command"/>
- <menuitem type="checkbox"
- id="ltn-show-completed-in-view"
- persist="checked"
- label="&calendar.completedtasks.checkbox.label;"
- accesskey="&calendar.completedtasks.checkbox.accesskey;"
- mode="calendar"
- observes="calendar_toggle_show_completed_in_view_command"/>
- <menuitem type="checkbox"
- id="ltn-multiday-rotated"
- label="&calendar.orientation.label;"
- accesskey="&calendar.orientation.accesskey;"
- mode="calendar"
- command="calendar_toggle_orientation_command"
- observes="calendar_toggle_orientation_command"/>
- <observes element="filterBroadcaster" attribute="value" onbroadcast="checkRadioControl(this.parentNode, document.getElementById(this.getAttribute('element')).getAttribute('value'));"/>
- <menuitem id="tasks-view-filter-all"
- name="filtergroup"
- value="all"
- type="radio"
- command="calendar_task_filter_command"
- mode="task"
- label="&calendar.task.filter.all.label;"
- accesskey="&calendar.task.filter.all.accesskey;"/>
- <menuitem id="tasks-view-filter-today"
- name="filtergroup"
- value="today"
- type="radio"
- command="calendar_task_filter_command"
- mode="task"
- label="&calendar.task.filter.today.label;"
- accesskey="&calendar.task.filter.today.accesskey;"/>
- <menuitem id="tasks-view-filter-next7days"
- name="filtergroup"
- value="next7days"
- type="radio"
- command="calendar_task_filter_command"
- mode="task"
- label="&calendar.task.filter.next7days.label;"
- accesskey="&calendar.task.filter.next7days.accesskey;"/>
- <menuitem id="tasks-view-filter-notstartedtasks"
- name="filtergroup"
- value="notstarted"
- type="radio"
- command="calendar_task_filter_command"
- mode="task"
- label="&calendar.task.filter.notstarted.label;"
- accesskey="&calendar.task.filter.notstarted.accesskey;"/>
- <menuitem id="tasks-view-filter-overdue"
- name="filtergroup"
- value="overdue"
- type="radio"
- command="calendar_task_filter_command"
- mode="task"
- label="&calendar.task.filter.overdue.label;"
- accesskey="&calendar.task.filter.overdue.accesskey;"/>
- <menuitem id="tasks-view-filter-completed"
- name="filtergroup"
- type="radio"
- value="completed"
- command="calendar_task_filter_command"
- mode="task"
- label="&calendar.task.filter.completed.label;"
- accesskey="&calendar.task.filter.completed.accesskey;"/>
- <menuitem id="tasks-view-filter-open"
- name="filtergroup"
- type="radio"
- value="open"
- command="calendar_task_filter_command"
- mode="task"
- label="&calendar.task.filter.open.label;"
- accesskey="&calendar.task.filter.open.accesskey;"/>
- </menupopup>
- </menu>
- <menuseparator id="before-Unifinder-Section" mode="calendar"/>
- <menuitem id="calendar-show-unifinder-menu"
- type="checkbox"
- checked="true"
- label="&showUnifinderCmd.label;"
- accesskey="&showUnifinderCmd.accesskey;"
- mode="calendar"
- command="calendar_show_unifinder_command"/>
- <menuseparator id="before-Calendar-View-Section" mode="calendar"/>
- <menuitem id="ltnChangeViewDay"
- label="&lightning.toolbar.day.label;"
- accesskey="&lightning.toolbar.day.accesskey;"
- type="radio"
- name="calendarMenuViews"
- mode="calendar"
- key="calendar-day-view-key"
- observes="calendar_day-view_command"/>
- <menuitem id="ltnChangeViewWeek"
- label="&lightning.toolbar.week.label;"
- accesskey="&lightning.toolbar.week.accesskey;"
- type="radio"
- name="calendarMenuViews"
- mode="calendar"
- key="calendar-week-view-key"
- observes="calendar_week-view_command"/>
- <menuitem id="ltnChangeViewMultiweek"
- label="&lightning.toolbar.multiweek.label;"
- accesskey="&lightning.toolbar.multiweek.accesskey;"
- type="radio"
- name="calendarMenuViews"
- mode="calendar"
- key="calendar-multiweek-view-key"
- observes="calendar_multiweek-view_command"/>
- <menuitem id="ltnChangeViewMonth"
- label="&lightning.toolbar.month.label;"
- accesskey="&lightning.toolbar.month.accesskey;"
- type="radio"
- name="calendarMenuViews"
- mode="calendar"
- key="calendar-month-view-key"
- observes="calendar_month-view_command"/>
- <menuseparator id="before-task-View-Section" mode="mail,calendar,task"/>
- <menuitem id="tasks-view-minimonth"
- type="checkbox"
- mode="calendar,task"
- label="&calendar.tasks.view.minimonth.label;"
- accesskey="&calendar.tasks.view.minimonth.accesskey;"
- command="calendar_toggle_minimonthpane_command"/>
- <menuitem id="tasks-view-filtertasks"
- type="checkbox"
- mode="task"
- label="&calendar.tasks.view.filtertasks.label;"
- accesskey="&calendar.tasks.view.filtertasks.accesskey;"
- command="calendar_toggle_filter_command"/>
- <menuitem id="tasks-view-calendarlist"
- type="checkbox"
- mode="calendar,task"
- label="&calendar.tasks.view.calendarlist.label;"
- accesskey="&calendar.tasks.view.calendarlist.accesskey;"
- command="calendar_toggle_calendarlist_command"/>
- <menuseparator id="before-today-pane"/>
- <menu id="today-pane-menu"
+ <menuseparator id="ltnViewMenuSeparator"
+ insertbefore="viewSortMenuSeparator"/>
+ <menu id="ltnTodayPaneMenu"
+ observes="calendar_in_foreground"
+ insertbefore="viewSortMenuSeparator"
label="&calendar.context.button.label;"
accesskey="&calendar.context.button.accesskey;">
- <menupopup>
+ <menupopup id="ltnTodayPaneMenuPopup">
<menuitem id="ltnShowTodayPane-2"
label="&todaypane.showTodayPane.label;"
accesskey="&todaypane.showTodayPane.accesskey;"
type="checkbox"
key="todaypanekey"
command="calendar_toggle_todaypane_command"/>
- <menuseparator id="before-displayminiday"/>
- <menuitem id="today-pane-displayminiday"
+ <menuseparator id="ltnSeparatorBeforeDisplayMiniday"/>
+ <menuitem id="ltnTodayPaneDisplayMiniday"
name="minidisplay"
value="miniday"
type="radio"
- oncommand="TodayPane.displayMiniSection(1)"
+ oncommand="TodayPane.displayMiniSection('miniday')"
label="&todaypane.showMiniday.label;"
accesskey="&todaypane.showMiniday.accesskey;"/>
- <menuitem id="today-pane-displayminimonth"
+ <menuitem id="ltnTodayPaneDisplayMinimonth"
name="minidisplay"
value="minimonth"
type="radio"
- oncommand="TodayPane.displayMiniSection(2)"
+ oncommand="TodayPane.displayMiniSection('minimonth')"
label="&todaypane.showMinimonth.label;"
accesskey="&todaypane.showMinimonth.accesskey;"/>
- <menuitem id="today-pane-displaynone"
+ <menuitem id="ltnTodayPaneDisplayNone"
name="minidisplay"
value="none"
type="radio"
- oncommand="TodayPane.displayMiniSection(3)"
+ oncommand="TodayPane.displayMiniSection('none')"
label="&todaypane.showNone.label;"
accesskey="&todaypane.showNone.accesskey;"/>
</menupopup>
</menu>
+ <menu id="ltnCalendarMenu"
+ insertbefore="viewSortMenuSeparator"
+ label="&lightning.menu.view.calendar.label;"
+ accesskey="&lightning.menu.view.calendar.accesskey;">
+ <menupopup id="ltnTasksMenuPopup">
+ <menuitem id="ltnChangeViewDay"
+ label="&lightning.toolbar.day.label;"
+ accesskey="&lightning.toolbar.day.accesskey;"
+ type="radio"
+ name="calendarMenuViews"
+ key="calendar-day-view-key"
+ observes="calendar_day-view_command"/>
+ <menuitem id="ltnChangeViewWeek"
+ label="&lightning.toolbar.week.label;"
+ accesskey="&lightning.toolbar.week.accesskey;"
+ type="radio"
+ name="calendarMenuViews"
+ key="calendar-week-view-key"
+ observes="calendar_week-view_command"/>
+ <menuitem id="ltnChangeViewMultiweek"
+ label="&lightning.toolbar.multiweek.label;"
+ accesskey="&lightning.toolbar.multiweek.accesskey;"
+ type="radio"
+ name="calendarMenuViews"
+ key="calendar-multiweek-view-key"
+ observes="calendar_multiweek-view_command"/>
+ <menuitem id="ltnChangeViewMonth"
+ label="&lightning.toolbar.month.label;"
+ accesskey="&lightning.toolbar.month.accesskey;"
+ type="radio"
+ name="calendarMenuViews"
+ key="calendar-month-view-key"
+ observes="calendar_month-view_command"/>
+ <menuseparator id="ltnBeforeCalendarViewSection"/>
+ <menuitem id="ltnTasksViewMinimonth"
+ type="checkbox"
+ label="&calendar.tasks.view.minimonth.label;"
+ accesskey="&calendar.tasks.view.minimonth.accesskey;"
+ command="calendar_toggle_minimonthpane_command"/>
+ <menuitem id="ltnTasksViewCalendarlist"
+ type="checkbox"
+ label="&calendar.tasks.view.calendarlist.label;"
+ accesskey="&calendar.tasks.view.calendarlist.accesskey;"
+ command="calendar_toggle_calendarlist_command"/>
+ <menuseparator id="ltnBeforeCurrentViewMenu"/>
+ <menu id="ltnCalendarCurrentViewMenu"
+ observes="calendar_mode_calendar"
+ label="&showCurrentView.label;"
+ accesskey="&showCurrentView.accesskey;">
+ <menupopup id="ltnCalendarCurrentViewMenuPopup">
+ <menuitem type="checkbox"
+ id="ltnWorkdaysOnlyMenuitem"
+ label="&calendar.onlyworkday.checkbox.label;"
+ accesskey="&calendar.onlyworkday.checkbox.accesskey;"
+ observes="calendar_toggle_workdays_only_command"/>
+ <menuitem type="checkbox"
+ id="ltnTasksInViewMenuitem"
+ label="&calendar.displaytodos.checkbox.label;"
+ accesskey="&calendar.displaytodos.checkbox.accesskey;"
+ observes="calendar_toggle_tasks_in_view_command"/>
+ <menuitem type="checkbox"
+ id="ltnShowCompletedInViewMenuItem"
+ label="&calendar.completedtasks.checkbox.label;"
+ accesskey="&calendar.completedtasks.checkbox.accesskey;"
+ observes="calendar_toggle_show_completed_in_view_command"/>
+ <menuitem type="checkbox"
+ id="ltnViewRotated"
+ label="&calendar.orientation.label;"
+ accesskey="&calendar.orientation.accesskey;"
+ command="calendar_toggle_orientation_command"
+ observes="calendar_toggle_orientation_command"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ <menu id="ltnTasksMenu"
+ observes="calendar_mode_task"
+ insertbefore="viewSortMenuSeparator"
+ label="&lightning.menu.view.tasks.label;"
+ accesskey="&lightning.menu.view.tasks.accesskey;">
+ <menupopup id="ltnTasksMenuPopup">
+ <observes element="filterBroadcaster"
+ attribute="value"
+ onbroadcast="checkRadioControl(this.parentNode, document.getElementById(this.getAttribute('element')).getAttribute('value'));"/>
+ <menuitem id="ltnTasksViewFilterTasks"
+ type="checkbox"
+ label="&calendar.tasks.view.filtertasks.label;"
+ accesskey="&calendar.tasks.view.filtertasks.accesskey;"
+ command="calendar_toggle_filter_command"/>
+ <menuseparator id="ltnTasksViewSeparator"/>
+ <menuitem id="ltnTasksViewFilterAll"
+ name="filtergroup"
+ value="all"
+ type="radio"
+ command="calendar_task_filter_command"
+ label="&calendar.task.filter.all.label;"
+ accesskey="&calendar.task.filter.all.accesskey;"/>
+ <menuitem id="ltnTasksViewFilterToday"
+ name="filtergroup"
+ value="today"
+ type="radio"
+ command="calendar_task_filter_command"
+ label="&calendar.task.filter.today.label;"
+ accesskey="&calendar.task.filter.today.accesskey;"/>
+ <menuitem id="ltnTasksViewFilterNext7days"
+ name="filtergroup"
+ value="next7days"
+ type="radio"
+ command="calendar_task_filter_command"
+ label="&calendar.task.filter.next7days.label;"
+ accesskey="&calendar.task.filter.next7days.accesskey;"/>
+ <menuitem id="ltnTasksViewFilterNotstartedtasks"
+ name="filtergroup"
+ value="notstarted"
+ type="radio"
+ command="calendar_task_filter_command"
+ label="&calendar.task.filter.notstarted.label;"
+ accesskey="&calendar.task.filter.notstarted.accesskey;"/>
+ <menuitem id="ltnTasksViewFilterOverdue"
+ name="filtergroup"
+ value="overdue"
+ type="radio"
+ command="calendar_task_filter_command"
+ label="&calendar.task.filter.overdue.label;"
+ accesskey="&calendar.task.filter.overdue.accesskey;"/>
+ <menuitem id="ltnTasksViewFilterCompleted"
+ name="filtergroup"
+ type="radio"
+ value="completed"
+ command="calendar_task_filter_command"
+ label="&calendar.task.filter.completed.label;"
+ accesskey="&calendar.task.filter.completed.accesskey;"/>
+ <menuitem id="ltnTasksViewFilterOpen"
+ name="filtergroup"
+ type="radio"
+ value="open"
+ command="calendar_task_filter_command"
+ label="&calendar.task.filter.open.label;"
+ accesskey="&calendar.task.filter.open.accesskey;"/>
+ </menupopup>
+ </menu>
</menupopup>
+ <menupopup id="menu_GoPopup">
+ <menuitem id="ltnGoToToday"
+ insertafter="goNextSeparator"
+ label="&goTodayCmd.label;"
+ accesskey="&goTodayCmd.accesskey;"
+ observes="calendar_go_to_today_command"
+ key="calendar-go-to-today-key"/>
+ </menupopup>
+
+ <menupopup id="menu_GoNextPopup">
+ <menuseparator id="ltnGoNextSeparator"/>
+ <!-- Label is set up automatically using the view id. When writing a
+ view extension, overlay this menuitem and add a label-<myviewtype>
+ attribute with the correct label -->
+ <menuitem id="calendar-go-menu-next"
+ label=""
+ label-day="&lightning.toolbar.day.label;"
+ label-week="&lightning.toolbar.week.label;"
+ label-multiweek="&lightning.toolbar.week.label;"
+ label-month="&lightning.toolbar.month.label;"
+ accesskey-day="&lightning.toolbar.day.accesskey;"
+ accesskey-week="&lightning.toolbar.week.accesskey;"
+ accesskey-multiweek="&lightning.toolbar.week.accesskey;"
+ accesskey-month="&lightning.toolbar.month.accesskey;"
+ observes="calendar_view_next_command"/>
+ </menupopup>
+ <menupopup id="menu_GoPreviousPopup">
+ <menuseparator id="ltnGoPreviousSeparator"/>
+ <!-- Label is set up automatically using the view id. When writing a
+ view extension, overlay this menuitem and add a label-<myviewtype>
+ attribute with the correct label -->
+ <menuitem id="calendar-go-menu-previous"
+ label=""
+ label-day="&lightning.toolbar.day.label;"
+ label-week="&lightning.toolbar.week.label;"
+ label-multiweek="&lightning.toolbar.week.label;"
+ label-month="&lightning.toolbar.month.label;"
+ accesskey-day="&lightning.toolbar.day.accesskey;"
+ accesskey-week="&lightning.toolbar.week.accesskey;"
+ accesskey-multiweek="&lightning.toolbar.week.accesskey;"
+ accesskey-month="&lightning.toolbar.month.accesskey;"
+ observes="calendar_view_prev_command"/>
+ </menupopup>
+
+ <menubar id="mail-menubar">
+ <menu id="menu_Event_Task"
+ label="&lightning.menu.eventtask.label;"
+ accesskey="&lightning.menu.eventtask.accesskey;"
+ insertafter="messageMenu">
+ <menupopup id="menu_Event_Task_Popup" onpopupshowing="changeMenuForTask(event)">
+ <menuitem id="ltnNewEvent2"
+ label="&event.new.event;"
+ accesskey="&event.new.event.accesskey;"
+ key="calendar-new-event-key"
+ command="calendar_new_event_command"
+ observes="calendar_new_event_command"/>
+ <menuitem id="ltnNewTask2"
+ label="&event.new.todo;"
+ accesskey="&event.new.todo.accesskey;"
+ key="calendar-new-todo-key"
+ command="calendar_new_todo_command"
+ observes="calendar_new_todo_command"/>
+ <menuseparator id="before-Calendar-Mode-Section"/>
+ <menuitem id="ltnMenuSwitchToCalendar"
+ label="&lightning.toolbar.calendar.label;"
+ accesskey="&lightning.toolbar.calendar.accesskey;"
+ command="switch2calendar"
+ key="openLightningKey"/>
+ <menuitem id="ltnMenuSwitchToTask"
+ label="&lightning.toolbar.task.label;"
+ accesskey="&lightning.toolbar.task.accesskey;"
+ command="switch2task"
+ key="openTasksKey"/>
+ <menuseparator id="ltnBeforeCalendarSection"/>
+ <!-- Menuitems have different schema just to match sunbird -->
+ <menuitem id="calendar-export-menu"
+ label="&calendar.export.label;"
+ accesskey="&calendar.export.accesskey;"
+ command="calendar_export_command"
+ observes="calendar_export_command"/>
+ <menuitem id="calendar-import-menu"
+ label="&calendar.import.label;"
+ accesskey="&calendar.import.accesskey;"
+ command="calendar_import_command"
+ observes="calendar_import_command"/>
+ <menuitem id="ltnPublishCalendar"
+ label="&calendar.publish.label;"
+ accesskey="&calendar.publish.accesskey;"
+ commmand="calendar_publish_calendar_command"
+ observes="calendar_publish_calendar_command"/>
+ <menuitem id="ltnDeleteSelectedCalendar"
+ label="&calendar.deletecalendar.label;"
+ accesskey="&calendar.deletecalendar.accesskey;"
+ command="calendar_delete_calendar_command"
+ observes="calendar_delete_calendar_command"/>
+ <menuseparator id="ltnBeforeTaskActions"/>
+ <menuitem id="ltnTaskActionsMarkCompletedMenuitem"
+ type="checkbox"
+ label="&calendar.context.markcompleted.label;"
+ accesskey="&calendar.context.markcompleted.accesskey;"
+ command="calendar_toggle_completed_command"
+ observes="calendar_toggle_completed_command"/>
+ <menu id="ltnTaskActionsPriorityMenuitem"
+ label="&calendar.context.priority.label;"
+ accesskey="&calendar.context.priority.accesskey;"
+ command="calendar_general-priority_command"
+ observes="calendar_general-priority_command">
+ <menupopup type="task-priority"/>
+ </menu>
+ <menu id="ltnTaskActionsProgressMenuitem"
+ label="&calendar.context.progress.label;"
+ accesskey="&calendar.context.progress.accesskey;"
+ command="calendar_general-progress_command"
+ observes="calendar_general-progress_command">
+ <menupopup type="task-progress"/>
+ </menu>
+ <menuseparator id="ltnBeforeUnifinderSection" />
+ <!-- menuitem has different schema just to match sunbird -->
+ <menuitem id="calendar-show-unifinder-menu"
+ type="checkbox"
+ checked="true"
+ label="&showUnifinderCmd.label;"
+ accesskey="&showUnifinderCmd.accesskey;"
+ command="calendar_show_unifinder_command"/>
+ </menupopup>
+ </menu>
+ </menubar>
+
<window id="messengerWindow">
- <broadcasterset id="calendar_broadcasters">
- <broadcaster id="filterBroadcaster" value="all"/>
- </broadcasterset>
-
<!-- Be sure to keep these sets, since they will be overlayed by
calendar/base/content/calendar-common-sets.xul -->
<commandset id="calendar_commands">
<command id="agenda_delete_event_command" oncommand="agendaListbox.deleteSelectedItem(false);"/>
<command id="agenda_edit_event_command" oncommand="agendaListbox.editSelectedItem(event);"/>
<command id="switch2mail" checked="true"
oncommand="document.getElementById('tabmail').selectTabByMode('folder')"/>
<command id="switch2calendar"
oncommand="document.getElementById('tabmail').openTab('calendar', document.getElementById('calendar-tab-button').getAttribute('tooltiptext'))"/>
<command id="switch2task"
oncommand="document.getElementById('tabmail').openTab('tasks', document.getElementById('task-tab-button').getAttribute('tooltiptext'))"/>
<command id="new_calendar_tab"
oncommand="document.getElementById('tabmail').openTab('calendar', document.getElementById('calendar-tab-button').getAttribute('tooltiptext'))"/>
<command id="new_task_tab"
oncommand="document.getElementById('tabmail').openTab('tasks', document.getElementById('task-tab-button').getAttribute('tooltiptext'))"/>
+ <command id="calendar_go_to_today_command"
+ oncommand="document.getElementById('tabmail').openTab('calendar', document.getElementById('calendar-tab-button').getAttribute('tooltiptext')); goToDate(now())"/>
</commandset>
<keyset id="calendar-keys">
<key id="openLightningKey" modifiers="accel" key="3" observes="new_calendar_tab"/>
<key id="openTasksKey" modifiers="accel" key="4" command="new_task_tab"/>
<key id="todaypanekey" command="calendar_toggle_todaypane_command" keycode="VK_F11"/>
<key id="calendar-new-event-key" key="&lightning.keys.event.new;" modifiers="accel" command="calendar_new_event_command"/>
<key id="calendar-new-todo-key" key="&lightning.keys.todo.new;" modifiers="accel" command="calendar_new_todo_command"/>
</keyset>
- <popupset id="calendar-popupset">
- <menupopup id="calendar-GoPopupMenu">
- <menuitem id="ltnGoToToday"
- label="&goTodayCmd.label;"
- accesskey="&goTodayCmd.accesskey;"
- mode="calendar"
- observes="calendar_go_to_today_command"
- key="calendar-go-to-today-key"/>
- <menuseparator id="before-ModeMenuItems"/>
- <menuitem id="ltnMenu_mail"
- type="radio"
- name="modemenu"
- label="&lightning.toolbar.mail.label;"
- accesskey="&lightning.toolbar.mail.accesskey;"
- command="switch2mail"
- key="key_mail" modifiers="accel"/>
- <menuitem id="ltnMenu_calendar"
- type="radio"
- name="modemenu"
- label="&lightning.toolbar.calendar.label;"
- accesskey="&lightning.toolbar.calendar.accesskey;"
- command="switch2calendar"
- key="openLightningKey"/>
- <menuitem id="ltnMenu_tasks"
- type="radio"
- name="modemenu"
- label="&lightning.toolbar.task.label;"
- accesskey="&lightning.toolbar.task.accesskey;"
- command="switch2task"
- key="openTasksKey"/>
- <menuseparator id="before-AddressBook"/>
- <menuitem id="addressBook-calendar" label="&addressBookCmd.label;"
- accesskey="&addressBookCmd.accesskey;"
- key="key_addressbook"
- oncommand="toAddressBook();"/>
- </menupopup>
- <menupopup id="calendarCalendarPopupMenu">
- <menuitem id="ltnNewEvent2" label="&event.new.event;"
- accesskey="&event.new.event.accesskey;"
- key="calendar-new-event-key"
- observes="calendar_new_event_command"
- command="calendar_new_event_command"
- position="2"/>
- <menuitem id="ltnNewTask2" label="&event.new.todo;"
- accesskey="&event.new.todo.accesskey;"
- key="calendar-new-todo-key"
- observes="calendar_new_todo_command"
- command="calendar_new_todo_command"
- position="3"/>
- <menuseparator id="firstCalendarSeparator" mode="calendar"/>
- <menuitem id="publishCalendar"
- label="&calendar.publish.label;"
- accesskey="&calendar.publish.accesskey;"
- commmand="calendar_publish_calendar_command"
- observes="calendar_publish_calendar_command"/>
- <menuseparator id="afterSubscription"/>
- <menuitem label="&calendar.context.newserver.label;"
- id="calpopup-new"
- accesskey="&calendar.context.newserver.accesskey;"
- command="calendar_new_calendar_command"
- observes="calendar_new_calendar_command"/>
- <menuitem id="ltnDeleteSelectedCalendar"
- label="&calendar.context.deleteserver.label;"
- accesskey="&calendar.context.deleteserver.accesskey;"
- command="calendar_delete_calendar_command"
- observes="calendar_delete_calendar_command"/>
- <menuseparator id="beforeProperties"/>
- <menuitem label="&calendar.properties.label;"
- id="calendarproperties"
- accesskey="&calendar.properties.accesskey;"
- command="calendar_edit_calendar_command"
- observes="calendar_edit_calendar_command"/>
- </menupopup>
- </popupset>
+ <broadcasterset id="calendar_broadcasters">
+ <broadcaster id="filterBroadcaster" value="all"/>
+ </broadcasterset>
+
+ <popupset id="calendar-popupset"/>
</window>
<hbox id="tabmail-buttons">
<toolbarbutton id="calendar-tab-button" command="new_calendar_tab"
tooltiptext="&lightning.toolbar.calendar.label;"/>
<toolbarbutton id="task-tab-button" command="new_task_tab"
tooltiptext="&lightning.toolbar.task.label;"/>
</hbox>
+
<tabpanels id="tabpanelcontainer">
<vbox id="calendarTabPanel">
<hbox id="calendarContent" flex="1">
<vbox id="ltnSidebar"
minwidth="100"
width="200"
persist="collapsed width">
<modevbox id="minimonth-pane" mode="calendar,task" broadcaster="modeBroadcaster" refcontrol="calendar_toggle_minimonthpane_command">
deleted file mode 100644
--- a/calendar/lightning/content/messenger-overlay-toolbar.js
+++ /dev/null
@@ -1,150 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Sun Microsystems code.
- *
- * The Initial Developer of the Original Code is Sun Microsystems.
- * Portions created by the Initial Developer are Copyright (C) 2007
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Michael Buettner <michael.buettner@sun.com>
- * Philipp Kewisch <mozilla@kewis.ch>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-/**
- * Global variables
- */
-var gCustomizeId;
-
-/**
- * the current mode is set to a string defining the current
- * mode we're in. allowed values are:
- * - 'mode'
- * - 'mail'
- * - 'calendar'
- * - 'task'
- */
-var gCurrentMode = 'mail';
-
-/**
- * ltnSwitch2Mail() switches to the mail mode
- */
-
-function ltnSwitch2Mail() {
- if (gCurrentMode != 'mail') {
- var switch2mail = document.getElementById("switch2mail");
- var switch2calendar = document.getElementById("switch2calendar");
- var switch2task = document.getElementById("switch2task");
- switch2mail.setAttribute("checked", "true");
- switch2calendar.removeAttribute("checked");
- switch2task.removeAttribute("checked");
-
- gCurrentMode = 'mail';
- swapPopupMenus();
- document.getElementById("modeBroadcaster").setAttribute("mode", gCurrentMode);
-
- document.commandDispatcher.updateCommands('calendar_commands');
-
- // Disable the rotate view menuitem
- document.getElementById("calendar_toggle_orientation_command")
- .setAttribute("disabled", "true");
- window.setCursor("auto");
- }
-}
-
-/**
- * ltnSwitch2Calendar() switches to the calendar mode
- */
-
-function ltnSwitch2Calendar() {
- if (gCurrentMode != 'calendar') {
- var switch2mail = document.getElementById("switch2mail");
- var switch2calendar = document.getElementById("switch2calendar");
- var switch2task = document.getElementById("switch2task");
- switch2mail.removeAttribute("checked");
- switch2calendar.setAttribute("checked", "true");
- switch2task.removeAttribute("checked");
-
- gCurrentMode = 'calendar';
- swapPopupMenus();
- document.getElementById("modeBroadcaster").setAttribute("mode", gCurrentMode);
-
- // display the calendar panel on the display deck
- var viewBox = document.getElementById("calendar-view-box");
- uncollapseElement(viewBox);
- var deck = document.getElementById("calendarDisplayDeck");
- deck.selectedPanel = viewBox;
-
- // show the last displayed type of calendar view
- showCalendarView(gLastShownCalendarView);
-
- document.commandDispatcher.updateCommands('calendar_commands');
-
- window.setCursor("auto");
- }
-}
-
-/**
- * ltnSwitch2Task() switches to the task mode
- */
-
-function ltnSwitch2Task() {
- if (gCurrentMode != 'task') {
- var switch2mail = document.getElementById("switch2mail");
- var switch2calendar = document.getElementById("switch2calendar");
- var switch2task = document.getElementById("switch2task");
- switch2mail.removeAttribute("checked");
- switch2calendar.removeAttribute("checked");
- switch2task.setAttribute("checked", "true");
-
- gCurrentMode = 'task';
- swapPopupMenus();
- document.getElementById("modeBroadcaster").setAttribute("mode", gCurrentMode);
-
- // display the task panel on the display deck
- var taskBox = document.getElementById("calendar-task-box");
- uncollapseElement(taskBox);
- var deck = document.getElementById("calendarDisplayDeck");
- deck.selectedPanel = taskBox;
-
- document.commandDispatcher.updateCommands('calendar_commands');
-
- window.setCursor("auto");
- }
-}
-
-const gCalSetupMailContext = {
- popup: function gCalSetupMailContext_popup() {
- var hasSelection = (GetFirstSelectedMessage() != null);
- // Disable the convert menu altogether.
- setElementValue("mailContext-calendar-convert-menu",
- !hasSelection && "true", "hidden");
- }
-};
-
-
-
--- a/calendar/lightning/content/messenger-overlay-toolbar.xul
+++ b/calendar/lightning/content/messenger-overlay-toolbar.xul
@@ -14,22 +14,23 @@
-
- The Original Code is calendar views.
-
- The Initial Developer of the Original Code is Oracle Corporation
- Portions created by the Initial Developer are Copyright (C) 2005
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- - Mike Shaver <shaver@mozilla.org>
- - Stuart Parmenter <stuart.parmenter@oracle.com>
+ - Mike Shaver <shaver@mozilla.org>
+ - Stuart Parmenter <stuart.parmenter@oracle.com>
- Vladimir Vukicevic <vladimir@pobox.com>
- - Simon Paquet <bugzilla@babylonsounds.com>
- - Berend Cornelius <berend.cornelius@sun.com>
+ - Simon Paquet <bugzilla@babylonsounds.com>
+ - Berend Cornelius <berend.cornelius@sun.com>
- Philipp Kewisch <mozilla@kewis.ch>
+ - Martin Schroeder <mschroeder@mozilla.x-home.org>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
@@ -57,21 +58,21 @@
<menu id="mailContext-calendar-convert-menu"
insertafter="mailContext-moveToFolderAgain"
label="&calendar.context.convertmenu.label;"
accesskey="&calendar.context.convertmenu.accesskey.mail;">
<menupopup id="mailContext-calendar-convert-menupopup">
<menuitem id="mailContext-calendar-convert-event-menuitem"
label="&calendar.context.convertmenu.event.label;"
accesskey="&calendar.context.convertmenu.event.accesskey;"
- oncommand="calendarCalendarButtonDNDObserver.onDropMessage(messenger.msgHdrFromURI(GetFirstSelectedMessage()))"/>
+ oncommand="calendarCalendarButtonDNDObserver.onDropMessage(gFolderDisplay.selectedMessage)"/>
<menuitem id="mailContext-calendar-convert-task-menuitem"
label="&calendar.context.convertmenu.task.label;"
accesskey="&calendar.context.convertmenu.task.accesskey;"
- oncommand="calendarTaskButtonDNDObserver.onDropMessage(messenger.msgHdrFromURI(GetFirstSelectedMessage()))"/>
+ oncommand="calendarTaskButtonDNDObserver.onDropMessage(gFolderDisplay.selectedMessage)"/>
</menupopup>
</menu>
</popup>
<toolbarbutton id="button-newmsg"
type="menu-button">
<menupopup id="button-newmsg-menupopup">
<menuitem id="newMsgButton-mail-menuitem"
--- a/calendar/lightning/jar.mn
+++ b/calendar/lightning/jar.mn
@@ -1,11 +1,10 @@
#filter substitution
lightning.jar:
-% content messagebody %content/messagebody/ contentaccessible=yes
% override chrome://messagebody/skin/imip.css chrome://lightning/skin/imip.css
% overlay chrome://messenger/content/messenger.xul chrome://lightning/content/lightning-migration.xul
% overlay chrome://messenger/content/msgAccountCentral.xul chrome://lightning/content/messenger-overlay-accountCentral.xul
% overlay chrome://messenger/content/messenger.xul chrome://lightning/content/messenger-overlay-sidebar.xul
% overlay chrome://messenger/content/messageWindow.xul chrome://lightning/content/imip-bar-overlay.xul
% overlay chrome://messenger/content/messageWindow.xul chrome://lightning/content/messenger-overlay-messageWindow.xul
% overlay chrome://lightning/content/messenger-overlay-sidebar.xul chrome://lightning/content/imip-bar-overlay.xul
% overlay chrome://messenger/content/preferences/preferences.xul chrome://lightning/content/messenger-overlay-preferences.xul
@@ -36,17 +35,16 @@ lightning.jar:
content/lightning/lightning-widgets.xml (content/lightning-widgets.xml)
content/lightning/messenger-overlay-sidebar.css (content/messenger-overlay-sidebar.css)
content/lightning/messenger-overlay-accountCentral.xul (content/messenger-overlay-accountCentral.xul)
content/lightning/messenger-overlay-messageWindow.xul (content/messenger-overlay-messageWindow.xul)
* content/lightning/messenger-overlay-sidebar.js (content/messenger-overlay-sidebar.js)
* content/lightning/messenger-overlay-sidebar.xul (content/messenger-overlay-sidebar.xul)
content/lightning/messenger-overlay-preferences.js (content/messenger-overlay-preferences.js)
content/lightning/messenger-overlay-preferences.xul (content/messenger-overlay-preferences.xul)
- content/lightning/messenger-overlay-toolbar.js (content/messenger-overlay-toolbar.js)
* content/lightning/messenger-overlay-toolbar.xul (content/messenger-overlay-toolbar.xul)
% skin lightning classic/1.0 %skin/lightning/
skin/lightning/imip.css (themes/@THEME@/imip.css)
skin/lightning/lightning.css (themes/@THEME@/lightning.css)
skin/lightning/accountCentral.css (themes/@THEME@/accountCentral.css)
skin/lightning/lightning-widgets.css (themes/@THEME@/lightning-widgets.css)
calendar.jar:
--- a/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.dtd
+++ b/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.dtd
@@ -44,25 +44,29 @@
<!ENTITY event.title.label "Edit Item" >
<!ENTITY newevent.from.label "From" >
<!ENTITY newevent.to.label "To" >
<!ENTITY newevent.attendees.notify.label "Notify attendees">
-<!ENTITY newevent.status.label "Status" >
-<!ENTITY newevent.status.accesskey "S" >
-<!ENTITY newevent.status.none.label "Not specified" >
-<!ENTITY newevent.status.cancelled.label "Cancelled" >
-<!ENTITY newevent.status.tentative.label "Tentative" >
-<!ENTITY newevent.status.confirmed.label "Confirmed" >
-<!ENTITY newevent.status.needsaction.label "Needs Action" >
-<!ENTITY newevent.status.inprogress.label "In Process" >
-<!ENTITY newevent.status.completed.label "Completed on" >
+<!ENTITY newevent.status.label "Status" >
+<!ENTITY newevent.status.accesskey "S" >
+<!ENTITY newevent.status.none.label "Not specified" >
+<!ENTITY newevent.status.none.accesskey "o" >
+<!ENTITY newevent.status.cancelled.label "Cancelled" >
+<!ENTITY newevent.status.cancelled.accesskey "n" >
+<!ENTITY newevent.status.tentative.label "Tentative" >
+<!ENTITY newevent.status.tentative.accesskey "T" >
+<!ENTITY newevent.status.confirmed.label "Confirmed" >
+<!ENTITY newevent.status.confirmed.accesskey "C" >
+<!ENTITY newevent.status.needsaction.label "Needs Action" >
+<!ENTITY newevent.status.inprogress.label "In Process" >
+<!ENTITY newevent.status.completed.label "Completed on" >
<!-- The following entity is for New Task dialog only -->
<!ENTITY newtodo.percentcomplete.label "% complete">
<!-- Menubar -->
<!ENTITY event.menu.file.label "File">
<!ENTITY event.menu.file.accesskey "F">
<!ENTITY event.menu.file.new.label "New">
@@ -72,16 +76,18 @@
<!ENTITY event.menu.file.new.message.label "Message">
<!ENTITY event.menu.file.new.message.accesskey "M">
<!ENTITY event.menu.file.new.contact.label "Address Book Contact">
<!ENTITY event.menu.file.new.contact.accesskey "C">
<!ENTITY event.menu.file.close.label "Close">
<!ENTITY event.menu.file.close.accesskey "C">
<!ENTITY event.menu.file.save.label "Save">
<!ENTITY event.menu.file.save.accesskey "S">
+<!ENTITY event.menu.item.delete.label "Delete…">
+<!ENTITY event.menu.item.delete.accesskey "D">
<!ENTITY event.menu.file.page.setup.label "Page Setup">
<!ENTITY event.menu.file.page.setup.accesskey "u">
<!ENTITY event.menu.file.print.label "Print">
<!ENTITY event.menu.file.print.accesskey "P">
<!ENTITY event.menu.edit.label "Edit">
<!ENTITY event.menu.edit.accesskey "E">
<!ENTITY event.menu.edit.undo.label "Undo">
@@ -160,16 +166,17 @@
<!ENTITY event.invite.attendees.accesskey "I">
<!ENTITY event.email.attendees.label "Compose E-Mail to All Attendees…">
<!ENTITY event.email.attendees.accesskey "A">
<!ENTITY event.email.tentative.attendees.label "Compose E-Mail to Undecided Attendees…">
<!ENTITY event.email.tentative.attendees.accesskey "U">
<!-- Toolbar -->
<!ENTITY event.toolbar.save.label "Save and Close">
+<!ENTITY event.toolbar.delete.label "Delete">
<!ENTITY event.toolbar.attendees.label "Invite Attendees">
<!ENTITY event.toolbar.spellcheck.label "Spellcheck">
<!ENTITY event.toolbar.privacy.label "Privacy">
<!ENTITY event.toolbar.attachments.label "Attach">
<!-- Main page -->
<!ENTITY event.title.textbox.label "Title:" >
<!ENTITY event.title.textbox.accesskey "T">
@@ -344,16 +351,19 @@
<!ENTITY event.freebusy.zoom "Zoom:">
<!ENTITY event.freebusy.plus "Next hour" >
<!ENTITY event.freebusy.minus "Previous hour" >
<!ENTITY event.freebusy.legend.free "Free" >
<!ENTITY event.freebusy.legend.busy "Busy" >
<!ENTITY event.freebusy.legend.busy_tentative "Tentative" >
<!ENTITY event.freebusy.legend.busy_unavailable "Out of Office" >
<!ENTITY event.freebusy.legend.unknown "No Information" >
+<!ENTITY event.attendee.role.required "Required Attendee">
+<!ENTITY event.attendee.role.optional "Optional Attendee">
+<!ENTITY event.attendee.role.chair "Chair">
<!-- Timezone dialog -->
<!ENTITY timezone.title.label "Please Specify the Timezone">
<!-- Read-Only dialog -->
<!ENTITY read.only.general.label "General">
<!ENTITY read.only.title.label "Title:">
<!ENTITY read.only.repeat.label "Repeat:">
--- a/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
+++ b/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
@@ -144,16 +144,22 @@ monthlyDayOfNth=day %1$S of every month;
# LOCALIZATION NOTE (monthlyLastDayOfNth):
# Edit recurrence window -> Recurrence pattern -> Monthly repeat rules
# %1$S - day of month
# #2 - month interval
# e.g. "the last day of every 3 months"
monthlyLastDayOfNth=the last day of the month; the last day of every #1 months
+# LOCALIZATION NOTE (monthlyEveryDayOfNth):
+# Edit recurrence window -> Recurrence pattern -> Monthly repeat rules
+# #2 - month interval
+# e.g. "every day of the month every 4 months"
+monthlyEveryDayOfNth=every day of every month;every day of the month every #2 months
+
# LOCALIZATION NOTE (repeatOrdinal...Nounclass...):
# Ordinal numbers nouns for every noun class (grammatical genders) of weekdays
# considered in 'repeatDetailsDayxNounclass' strings. For languages that need
# localization according to genders or noun classes.
# Nounclass1 <-> Masculine gender; Nounclass2 <-> Feminine gender.
# Add 'repeatOrdinal...Nounclass' strings with suffix 3, 4 and so on for
# languages with more than two noun classes for weekdays. In this case
# must be added corresponding rule strings with 'Nounclass...' suffix and
--- a/calendar/locales/en-US/chrome/calendar/calendar.properties
+++ b/calendar/locales/en-US/chrome/calendar/calendar.properties
@@ -561,13 +561,16 @@ datetimeIntervalTaskWithoutDueDate=start
# used for intervals in task with only due date
# displayed form is 'due date 5 Jan 2006 13:00'
# (showed only in exported calendar in Html format)
# %1$S will be replaced with the date of the due date
# %2$S will be replaced with the time of the due date
datetimeIntervalTaskWithoutStartDate=due date %1$S %2$S
deleteTaskLabel=Delete Task
+deleteTaskMessage=Do you really want to delete this Task?
deleteTaskAccesskey=l
deleteItemLabel=Delete
+deleteItemMessage=Do you really want to delete this Item?
deleteItemAccesskey=l
deleteEventLabel=Delete Event
+deleteEventMessage=Do you really want to delete this Event?
deleteEventAccesskey=l
--- a/calendar/locales/en-US/chrome/calendar/menuOverlay.dtd
+++ b/calendar/locales/en-US/chrome/calendar/menuOverlay.dtd
@@ -57,24 +57,27 @@
<!ENTITY calendar.print.label "Print…">
<!ENTITY calendar.print.key "P">
<!ENTITY calendar.print.accesskey "P">
<!ENTITY calendar.import.label "Import…">
<!ENTITY calendar.import.key "I">
<!ENTITY calendar.import.accesskey "I">
-<!ENTITY calendar.export.calendar.label "Export Calendar…">
-<!ENTITY calendar.export.calendar.accesskey "E">
+<!ENTITY calendar.export.label "Export…">
+<!ENTITY calendar.export.accesskey "E">
<!ENTITY calendar.export.selection.label "Export Selection…">
<!ENTITY calendar.export.selection.accesskey "S">
<!ENTITY calendar.publish.label "Publish…">
<!ENTITY calendar.publish.accesskey "b">
+<!ENTITY calendar.deletecalendar.label "Delete Selected Calendar…">
+<!ENTITY calendar.deletecalendar.accesskey "D">
+
<!ENTITY calendar.menu.customize.label "Customize…">
<!ENTITY calendar.menu.customize.accesskey "C">
<!ENTITY showUnifinderCmd.label "Find Events">
<!ENTITY showUnifinderCmd.accesskey "F">
<!ENTITY calendar.displaytodos.checkbox.label "Show Tasks in Calendar">
<!ENTITY calendar.displaytodos.checkbox.accesskey "T">
@@ -94,18 +97,13 @@
<!ENTITY goPreviousCmd.month.label "Previous Month">
<!ENTITY goPreviousCmd.accesskey "P">
<!ENTITY goNextCmd.day.label "Next Day">
<!ENTITY goNextCmd.week.label "Next Week">
<!ENTITY goNextCmd.month.label "Next Month">
<!ENTITY goNextCmd.all.accesskey "e">
-<!ENTITY addressBookCmd.label "Address Book">
-<!ENTITY addressBookCmd.accesskey "A">
-
-<!ENTITY calendar.importcalendar.label "Import Calendar…">
-
<!ENTITY showCurrentView.label "Current View">
<!ENTITY showCurrentView.accesskey "V">
<!ENTITY calendar.properties.label "Calendar Properties…">
<!ENTITY calendar.properties.accesskey "C">
--- a/calendar/locales/en-US/chrome/lightning/lightning.dtd
+++ b/calendar/locales/en-US/chrome/lightning/lightning.dtd
@@ -45,32 +45,39 @@
- your editor isn't using UTF-8 encoding and may munge up the document!
-->
<!-- Tools menu -->
<!ENTITY lightning.taskLabel "Lightning">
<!-- New menu popup in File menu -->
<!ENTITY lightning.menupopup.new.event.label "Event…">
+<!ENTITY lightning.menupopup.new.event.accesskey "E">
<!ENTITY lightning.menupopup.new.task.label "Task…">
+<!ENTITY lightning.menupopup.new.task.accesskey "T">
<!ENTITY lightning.menupopup.new.calendar.label "Calendar…">
+<!ENTITY lightning.menupopup.new.calendar.accesskey "n">
<!-- Open menu popup in File menu -->
<!ENTITY lightning.menupopup.open.label "Open">
<!ENTITY lightning.menupopup.open.accesskey "O">
<!ENTITY lightning.menupopup.open.message.label "Saved Message…">
<!ENTITY lightning.menupopup.open.message.accesskey "M">
<!ENTITY lightning.menupopup.open.calendar.label "Calendar File…">
<!ENTITY lightning.menupopup.open.calendar.accesskey "C">
-<!-- Messenger Sidebar -->
-<!ENTITY lightning.calendar.label "Calendar">
-<!ENTITY lightning.calendar.accesskey "C">
-<!ENTITY lightning.tasks.label "Tasks">
-<!ENTITY lightning.tasks.accesskey "T">
+<!-- View Menu -->
+<!ENTITY lightning.menu.view.calendar.label "Calendar">
+<!ENTITY lightning.menu.view.calendar.accesskey "n">
+<!ENTITY lightning.menu.view.tasks.label "Tasks">
+<!ENTITY lightning.menu.view.tasks.accesskey "k">
+
+<!-- Events and Tasks menu -->
+<!ENTITY lightning.menu.eventtask.label "Events and Tasks">
+<!ENTITY lightning.menu.eventtask.accesskey "n">
<!-- Mode Toolbar -->
<!ENTITY lightning.toolbar.mail.label "Mail">
<!ENTITY lightning.toolbar.mail.accesskey "M">
<!ENTITY lightning.toolbar.calendar.label "Calendar">
<!ENTITY lightning.toolbar.calendar.accesskey "C">
<!ENTITY lightning.toolbar.task.label "Tasks">
<!ENTITY lightning.toolbar.task.accesskey "T">
--- a/calendar/locales/en-US/chrome/sunbird/menuOverlay.dtd
+++ b/calendar/locales/en-US/chrome/sunbird/menuOverlay.dtd
@@ -159,16 +159,19 @@
<!ENTITY releaseCmd.accesskey "R">
<!ENTITY aboutCmd.label "About &brandFullName;">
<!ENTITY aboutCmd.accesskey "A">
<!ENTITY updateCmd.label "Check for Updates…">
<!ENTITY updateCmd.accesskey "o">
+<!ENTITY sunbird.export.calendar.label "Export Calendar…">
+<!ENTITY sunbird.export.calendar.accesskey "E">
+
<!-- Mac OS X "Window" menu items -->
<!ENTITY windowMenu.label "Window">
<!ENTITY minimizeWindow.label "Minimize">
<!ENTITY zoomWindow.label "Zoom">
<!ENTITY minimizeWindow.key "M">
<!ENTITY bringAllToFront.label "Bring All to Front">
<!-- Mac OS X Application menu items -->
--- a/calendar/providers/caldav/calDavCalendar.js
+++ b/calendar/providers/caldav/calDavCalendar.js
@@ -1298,16 +1298,17 @@ calDavCalendar.prototype = {
try {
cal.LOG("CalDAV: Status " + request.responseStatus +
" on initial PROPFIND for calendar " + thisCalendar.name);
} catch (ex) {
cal.LOG("CalDAV: Error without status on initial PROPFIND for calendar " +
thisCalendar.name);
thisCalendar.completeCheckServerInfo(aChangeLogListener,
Components.interfaces.calIErrors.DAV_NOT_DAV);
+ return;
}
var wwwauth;
try {
wwwauth = request.getRequestHeader("Authorization");
thisCalendar.mAuthScheme = wwwauth.split(" ")[0];
} catch (ex) {
// no auth header could mean a public calendar
thisCalendar.mAuthScheme = "none";
--- a/calendar/resources/content/calendar.js
+++ b/calendar/resources/content/calendar.js
@@ -240,15 +240,9 @@ function sbSwitchToView(newView) {
if (newView == "multiweek") {
mwWeeksCommand.removeAttribute("disabled");
} else {
mwWeeksCommand.setAttribute("disabled", true);
}
// Call the common view switching code in calendar-views.js
switchToView(newView);
-
- var labelAttribute = "label-" + newView + "-view";
- var prevCommand = document.getElementById("calendar-go-menu-previous");
- prevCommand.setAttribute("label", prevCommand.getAttribute(labelAttribute));
- var nextCommand = document.getElementById("calendar-go-menu-next");
- nextCommand.setAttribute("label", nextCommand.getAttribute(labelAttribute));
}
--- a/calendar/resources/content/calendarCreation.xul
+++ b/calendar/resources/content/calendarCreation.xul
@@ -9,80 +9,79 @@
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is Mozilla Calendar Code.
-
- - The Initial Developer of the Original Code is Stuart Parmenter.
+ - The Initial Developer of the Original Code is
+ - Stuart Parmenter.
- Portions created by the Initial Developer are Copyright (C) 2005
- the Initial Developer. All Rights Reserved.
-
- - Contributor(s): Mike Shaver <shaver@mozilla.org>
- - Michiel van Leeuwen <mvl@exedo.nl>
- - Simon Paquet <bugzilla@babylonsounds.com>
- - Gary van der Merwe <garyvdm@gmail.com>
- - Philipp Kewisch <mozilla@kewis.ch>
+ - Contributor(s):
+ - Mike Shaver <shaver@mozilla.org>
+ - Michiel van Leeuwen <mvl@exedo.nl>
+ - Simon Paquet <bugzilla@babylonsounds.com>
+ - Gary van der Merwe <garyvdm@gmail.com>
+ - Philipp Kewisch <mozilla@kewis.ch>
+ - Martin Schroeder <mschroeder@mozilla.x-home.org>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the LGPL or the GPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
-<!-- Style sheets -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://calendar/skin/calendar-creation-wizard.css" type="text/css"?>
-<!DOCTYPE dialog
-[
+<!DOCTYPE dialog [
<!ENTITY % dtd1 SYSTEM "chrome://calendar/locale/calendarCreation.dtd" > %dtd1;
<!ENTITY % dtd2 SYSTEM "chrome://calendar/locale/calendar.dtd" > %dtd2;
]>
-<!-- The Window -->
-
<wizard id="calendar-wizard"
title="&wizard.title;"
windowtype="Calendar:NewCalendarWizard"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onwizardfinish=""
persist="screenX screenY">
<script type="application/javascript" src="chrome://calendar/content/calUtils.js"/>
<script type="application/javascript" src="chrome://calendar/content/calendarCreation.js"/>
- <wizardpage pageid="initialPage"
+ <wizardpage pageid="initialPage"
next="locationPage"
label="&wizard.label;"
description="&wizard.description;"
onpageshow="checkRequired();"
onpageadvanced="onInitialAdvance();">
<description>&initialpage.description;</description>
<radiogroup id="calendar-type">
<radio value="local" label="&initialpage.computer.label;" selected="true"/>
<radio value="remote" label="&initialpage.network.label;"/>
</radiogroup>
</wizardpage>
<wizardpage pageid="locationPage"
next="customizePage"
label="&wizard.label;"
onpageshow="initLocationPage();"
- onpageadvanced="return prepareCreateCalendar()"
+ onpageadvanced="return prepareCreateCalendar();"
description="&wizard.description;">
<description>&locationpage.description;</description>
<grid>
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
@@ -98,17 +97,17 @@
<label value="&calendarproperties.location.label;" control="calendar-uri"/>
<!-- Due to bug 128066, dropping urls here doesn't trigger
events. To work around, we use the dragexit handler,
which is triggered. Unfortunately, the more logical
choice of dragdrop doesn't work here either.-->
<textbox id="calendar-uri"
required="true"
oninput="checkRequired();"
- ondragexit="checkRequired()"/>
+ ondragexit="checkRequired();"/>
</row>
<!--
<description>&locationpage.login.description;</description>
<row align="center">
<label value="&locationpage.username.label;" control="calendar-username"/>
<textbox id="calendar-username"/>
</row>
<row align="center">
@@ -119,17 +118,17 @@
</rows>
</grid>
</wizardpage>
<wizardpage pageid="customizePage"
description="&custompage.shortdescription;"
label="&wizard.label;"
next="finishPage"
- onpageshow="initCustomizePage()"
+ onpageshow="initCustomizePage();"
onpageadvanced="doCreateCalendar();">
<description>&custompage.longdescription;</description>
<grid>
<columns>
<column/>
<column flex="1"/>
</columns>
<rows id="customize-rows">
@@ -144,21 +143,21 @@
<colorpicker id="calendar-color"
class="small-margin"
type="button"
palettename="standard"/>
</hbox>
</row>
<row id="customize-suppressAlarms-row" align="center">
<label value="&calendarproperties.firealarms.label;:" control="fire-alarms"/>
- <checkbox id="fire-alarms" checked="true"/>
+ <checkbox id="fire-alarms" checked="true" class="checkbox-no-label"/>
</row>
</rows>
</grid>
</wizardpage>
- <wizardpage pageid="finishPage"
+ <wizardpage pageid="finishPage"
description="&finishpage.shortdescription;"
label="&wizard.label;"
onpageshow="setCanRewindFalse();">
<description>&finishpage.longdescription;</description>
</wizardpage>
</wizard>
--- a/calendar/sunbird/app/profile/sunbird.js
+++ b/calendar/sunbird/app/profile/sunbird.js
@@ -60,19 +60,27 @@ pref("calendar.alarms.defaultsnoozelengt
pref("calendar.alarms.indicator.show", true);
pref("calendar.alarms.indicator.totaltime", 3600);
pref("calendar.autorefresh.enabled", true);
pref("calendar.autorefresh.timeout", 30);
pref("calendar.date.format", 0);
pref("calendar.event.defaultlength", 60);
// Do NOT set this. If it is unset, we guess the timezone from the system
//pref("calendar.timezone.local", "America/New_York);
+
+// start and end work hour for day and week views
pref("calendar.view.daystarthour", 8);
pref("calendar.view.dayendhour", 17);
+
+// number of visible hours for day and week views
pref("calendar.view.visiblehours", 9);
+
+// If true, mouse scrolling via shift+wheel will be enabled
+pref("calendar.view.mousescroll", true);
+
pref("calendar.weeks.inview", 4);
pref("calendar.previousweeks.inview", 0);
// Disable use of worker threads. Restart needed.
pref("calendar.threading.disabled", false);
// default transparency of allday items; could be switched to e.g. "OPAQUE":
pref("calendar.allday.defaultTransparency", "TRANSPARENT");
--- a/calendar/sunbird/base/content/calendar-menubar.inc
+++ b/calendar/sunbird/base/content/calendar-menubar.inc
@@ -85,18 +85,18 @@
accesskey="&calendar.import.accesskey;"
observes="calendar_import_command"/>
<!-- export: getting data out of existing files -->
<menuitem id="calendar-export-menu"
label="&calendar.export.selection.label;"
accesskey="&calendar.export.selection.accesskey;"
observes="calendar_export_selection_command"/>
<menuitem id="calendar-export-calendar-menu"
- label="&calendar.export.calendar.label;"
- accesskey="&calendar.export.calendar.accesskey;"
+ label="&sunbird.export.calendar.label;"
+ accesskey="&sunbird.export.calendar.accesskey;"
observes="calendar_export_command"/>
<menuseparator id="calendar-importexport-sep"/>
<menuitem id="calendar-publish-menu"
label="&calendar.publish.selection.label;"
accesskey="&calendar.publish.selection.accesskey;"
observes="calendar_publish_selected_events_command"/>
<menuitem id="calendar-publish-calendar-menu"
label="&calendar.publish.calendar.label;"
@@ -345,32 +345,33 @@
accesskey="&goTodayCmd.accesskey;"
observes="calendar_go_to_today_command"/>
<menuitem id="calendar-go-menu-date"
key="go_to_date_key"
label="&goDateCmd.label;"
accesskey="&goDateCmd.accesskey;"
observes="go_date_command"/>
<menuseparator/>
- <!-- Label is set appropriate to the variable newView in
- sbSwitchToView(newView) of calendar.js -->
+ <!-- Label is set up automatically using the view id. When writing a
+ view extension, overlay this menuitem and add a label-<myviewtype>
+ attribute with the correct label -->
<menuitem id="calendar-go-menu-previous"
label=""
- label-day-view="&goPreviousCmd.day.label;"
- label-week-view="&goPreviousCmd.week.label;"
- label-multiweek-view="&goPreviousCmd.week.label;"
- label-month-view="&goPreviousCmd.month.label;"
+ label-day="&goPreviousCmd.day.label;"
+ label-week="&goPreviousCmd.week.label;"
+ label-multiweek="&goPreviousCmd.week.label;"
+ label-month="&goPreviousCmd.month.label;"
accesskey="&goPreviousCmd.accesskey;"
observes="calendar_view_prev_command"/>
<menuitem id="calendar-go-menu-next"
label=""
- label-day-view="&goNextCmd.day.label;"
- label-week-view="&goNextCmd.week.label;"
- label-multiweek-view="&goNextCmd.week.label;"
- label-month-view="&goNextCmd.month.label;"
+ label-day="&goNextCmd.day.label;"
+ label-week="&goNextCmd.week.label;"
+ label-multiweek="&goNextCmd.week.label;"
+ label-month="&goNextCmd.month.label;"
accesskey="&goNextCmd.all.accesskey;"
observes="calendar_view_next_command"/>
</menupopup>
</menu>
<menu id="menu_Tools"
label="&toolsMenu.label;"
accesskey="&toolsMenu.accesskey;">
--- a/calendar/sunbird/base/content/calendar-sets.inc
+++ b/calendar/sunbird/base/content/calendar-sets.inc
@@ -66,16 +66,17 @@
<!-- Sunbird specific commands -->
<command id="open_local_calendar_command" oncommand="openLocalCalendar()"/>
<command id="go_date_command" oncommand="pickAndGoToDate()"/>
<command id="cmd_quitApplication" oncommand="goQuitApplication()"/>
<command id="close_calendar_command" oncommand="closeCalendar()"/>
+ <command id="calendar_go_to_today_command" oncommand="goToDate(now())"/>
#ifdef XP_MACOSX
<command id="minimizeWindowCmd" oncommand="window.minimize();"/>
<command id="zoomWindowCmd" oncommand="zoomWindow();"/>
#endif
</commandset>
<keyset id="calendar-keys">
deleted file mode 100644
--- a/calendar/test/homegrown/caldav/jsDriver-hook.pl
+++ /dev/null
@@ -1,2 +0,0 @@
-
-../memory
deleted file mode 100644
--- a/calendar/test/homegrown/caldav/shell.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// init file for each test in this directory. executed in the xpcshell before
-// starting each test
-
-// create a memory calendar
-createCal("caldav");
-
deleted file mode 100644
--- a/calendar/test/homegrown/ics2ics.js
+++ /dev/null
@@ -1,26 +0,0 @@
-var str = "BEGIN:VCALENDAR\n";
- str += "BEGIN:VEVENT\n";
- str += "CREATED:20050104T211235\n";
- str += "LAST-MODIFIED:20050108T182742\n";
- str += "DTSTAMP:20050104T211235\n";
- str += "UID:uuid:1104873174977\n";
- str += "SUMMARY:teste\n";
- str += "DTSTART:20050107T094500\n";
- str += "DTEND:20050107T104500\n";
- str += "RRULE:FREQ=DAILY;COUNT=43;INTERVAL=5\n";
- str += "ATTENDEE:MAILTO:test@example.com\n";
- str += "END:VEVENT\n";
- str += "END:VCALENDAR\n";
-
-var icsServ = Components.classes["@mozilla.org/calendar/ics-service;1"]
- .getService(Components.interfaces.calIICSService);
-
-var calComp = icsServ.parseICS(str, null);
-var subComp = calComp.getFirstSubcomponent("VEVENT");
-var event = Components.classes["@mozilla.org/calendar/event;1"]
- .createInstance(Components.interfaces.calIEvent);
-event.icalComponent = subComp;
-
-var newCalComp = event.icalComponent;
-dump(newCalComp.serializeToICS());
-
deleted file mode 100644
--- a/calendar/test/homegrown/memory/add-get-delete-event.js
+++ /dev/null
@@ -1,26 +0,0 @@
-// create an event
-var item = createItem();
-
-item.title = "Test Event";
-
-// add it to the calendar
-var newItem = addItem(item);
-dump("newItem = " + newItem + "\n");
-
-// XXX what should happen to untyped item?
-
-// XXX compare newItem to item
-
-// get it from the calendar
-var gottenItem = getItem(newItem.id);
-dump("gottenItem = " + gottenItem + "\n");
-
-// XXX compare gottenItem to item
-
-// delete it from the calendar
-var deletedItem = deleteItem(gottenItem);
-dump("deletedItem = " + deletedItem + "\n");
-
-// XXX compare deletedItem to gottenItem
-
-// XXX make sure getting and deleting again both fail
deleted file mode 100644
--- a/calendar/test/homegrown/memory/add-get-delete-events.js
+++ /dev/null
@@ -1,37 +0,0 @@
-// create some events
-var item1 = createItem();
-item1.title = "Test Event 1";
-var item2 = createItem();
-item2.title = "Test Event 2";
-
-// add them to the calendar
-var newItem1 = addItem(item1);
-dump("newItem1 = " + newItem1 + "\n");
-var newItem2 = addItem(item2);
-dump("newItem2 = " + newItem2 + "\n");
-
-// XXX what should happen untyped items?
-
-// XXX compare newItems to items
-
-// get them from the calendar
-var gottenItems = getItems(Ci.calICalendar.ITEM_FILTER_COMPLETED_ALL |
- Ci.calICalendar.ITEM_FILTER_TYPE_EVENT, 0, null,
- null);
-
-dump("gottenItems = " + gottenItems + "\n");
-
-// XXX compare gottenItem to item
-
-// delete it from the calendar
-var deletedItem1 = deleteItem(gottenItems[0]);
-dump("deletedItem1 = " + deletedItem1 + "\n");
-var deletedItem2 = deleteItem(gottenItems[1]);
-dump("deletedItem2 = " + deletedItem2 + "\n");
-
-// XXX compare deletedItem to gottenItem
-
-// XXX make sure getting and deleting again both fail
-
-
-
deleted file mode 100644
--- a/calendar/test/homegrown/memory/shell.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// init file for each test in this directory. executed in the xpcshell before
-// starting each test
-
-// create a memory calendar
-createCal("memory");
-
deleted file mode 100644
--- a/calendar/test/homegrown/misc/attendee.js
+++ /dev/null
@@ -1,156 +0,0 @@
-var eventClass = C.classes["@mozilla.org/calendar/event;1"];
-var eventIID = C.interfaces.calIEvent;
-
-var attendeeClass = C.classes["@mozilla.org/calendar/attendee;1"];
-var attendeeIID = C.interfaces.calIAttendee;
-
-dump("* Creating event.\n");
-var e = eventClass.createInstance(eventIID);
-dump("* Creating attendee.\n");
-var a1 = attendeeClass.createInstance(attendeeIID);
-dump("* Testing attendee set/get.\n");
-var properties = ["id", "commonName", "rsvp", "role", "participationStatus",
- "userType"];
-var values = ["myid", "mycn", "TRUE", attendeeIID.ROLE_CHAIR,
- attendeeIID.PARTSTAT_DECLINED,
- attendeeIID.CUTYPE_RESOURCE];
-if (properties.length != values.length)
- throw "Test bug: mismatched properties and values arrays!";
-
-for (var i = 0; i < properties.length; i++) {
- a1[properties[i]] = values[i];
- if (a1[properties[i]] != values[i]) {
- throw "FAILED! " + properties[i] + " not set to " + values[i];
- }
-}
-
-dump("* Adding attendee to event.\n");
-e.addAttendee(a1);
-dump("* Adding 2nd attendee to event.\n");
-var a2 = attendeeClass.createInstance(attendeeIID);
-a2.id = "myid2";
-e.addAttendee(a2);
-
-dump("* Finding by ID.\n");
-
-function findById(id, a) {
- var foundAttendee = e.getAttendeeById(id);
- if (foundAttendee != a) {
- throw "FAILED! wrong attendee returned for + '" +
- id + "' (got " + foundAttendee + ", expected " + a + ")";
- }
-}
-
-findById("myid", a1);
-findById("myid2", a2);
-
-dump("* Searching getAttendees results\n");
-var found1, found2;
-
-function findAttendeesInResults(expectedCount) {
- if (expectedCount == undefined)
- throw "TEST BUG: expectedCount not passed to findAttendeesInResults";
- var countObj = {};
- dump(" Getting all attendees.\n");
- var allAttendees = e.getAttendees(countObj);
- if (countObj.value != allAttendees.length) {
- throw "FAILED! out count (" + countObj.value + ") != .length (" +
- allAttendees.length + ")";
- }
-
- if (allAttendees.length != expectedCount) {
- throw "FAILED! expected to get back " + expectedCount +
- " attendees, got " + allAttendees.length;
- }
-
- found1 = false, found2 = false;
- for (var i = 0; i < expectedCount; i++) {
- if (allAttendees[i] == a1)
- found1 = true;
- else if (allAttendees[i] == a2)
- found2 = true;
- else {
- throw "FAILED! unknown attendee " + allAttendees[i] +
- " (we added " + a1 + " and " + a2 + + ")";
- }
- }
-}
-findAttendeesInResults(2);
-if (!found1)
- throw "FAILED! didn't find attendee1 (" + a1 + ") in results";
-if (!found2)
- throw "FAILED! didn't find attendee2 (" + a2 + ") in results";
-
-dump("* Removing attendee.\n");
-e.removeAttendee(a1);
-if (e.getAttendeeById(a1.id) != null)
- throw "FAILED! got back removed attendee " + a1 + " by id";
-findById("myid2", a2);
-found1 = false, found2 = false;
-findAttendeesInResults(1);
-if (found1)
- throw "FAILED! found removed attendee " + a1 + " in getAttendees results";
-if (!found2) {
- throw "FAILED! didn't find remaining attendee " + a2 +
- " in getAttendees results";
-}
-
-dump("* Readding attendee.\n");
-e.addAttendee(a1);
-findById("myid", a1);
-findById("myid2", a2);
-findAttendeesInResults(2);
-if (!found1)
- throw "FAILED! didn't find attendee1 (" + a1 + ") in results";
-if (!found2)
- throw "FAILED! didn't find attendee2 (" + a2 + ") in results";
-
-dump("* Making attendee immutable.\n");
-a1.makeImmutable();
-function testImmutability(a) {
- if (a.isMutable) {
- throw "FAILED! Attendee " + a +
- " should be immutable, but claims otherwise";
- }
- for (var i = 0; i < properties.length; i++) {
- var old = a[properties[i]];
- var threw;
- try {
- a[properties[i]] = old + 1;
- threw = false;
- } catch (e) {
- threw = true;
- }
- if (!threw) {
- throw "FAILED! no error thrown setting " + properties[i] +
- "on immutable attendee " + a;
- }
- if (a[properties[i]] != old) {
- throw "FAILED! setting " + properties[i] + " on " + a +
- " threw, but changed value anyway!";
- }
- }
-}
-
-testImmutability(a1);
-dump("* Testing cascaded immutability (event -> attendee).\n");
-e.makeImmutable();
-testImmutability(a2);
-
-dump("* Testing cloning\n");
-var ec = e.clone();
-var clonedatts = ec.getAttendees({});
-var atts = e.getAttendees({});
-if (atts.length != clonedatts.length) {
- throw "FAILED! cloned event has " + clonedatts.length +
- "attendees, original had " + atts.length;
-}
-for (i = 0; i < clonedatts.length; i++) {
- if (atts[i] == clonedatts[i])
- throw "FAILED! attendee " + atts[i].id + " shared with clone!";
- if (atts[i].id != clonedatts[i].id) {
- throw "FAILED! id mismatch on index " + i + ": " +
- atts[i].id + " != " + clonedatts[i].id;
- }
-}
-dump("PASSED!\n");
deleted file mode 100644
--- a/calendar/test/homegrown/misc/ics-roundtrip.js
+++ /dev/null
@@ -1,41 +0,0 @@
-var eventClass = C.classes["@mozilla.org/calendar/event;1"];
-var eventIID = C.interfaces.calIEvent;
-
-dump("* Creating event.\n");
-var e = eventClass.createInstance(eventIID);
-
-var ics_xmas =
-'BEGIN:VCALENDAR\nPRODID:-//ORACLE//NONSGML CSDK 9.0.5 - CalDAVServlet 9.0.5//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:20041119T052239Z-1000472-1-5c0746bb-Oracle\nORGANIZER;X-ORACLE-GUID=E9359406791C763EE0305794071A39A4;CN=Simon Vaillan\n court:mailto:simon.vaillancourt@oracle.com\nSEQUENCE:0\nDTSTAMP:20041124T010028Z\nCREATED:20041119T052239Z\nX-ORACLE-EVENTINSTANCE-GUID:I1+16778354+1+1+438153759\nX-ORACLE-EVENT-GUID:E1+16778354+1+438153759\nX-ORACLE-EVENTTYPE:DAY EVENT\nTRANSP:TRANSPARENT\nSUMMARY:Christmas\nSTATUS:CONFIRMED\nPRIORITY:0\nDTSTART;VALUE=DATE:20041125\nDTEND;VALUE=DATE:20041125\nCLASS:PUBLIC\nATTENDEE;X-ORACLE-GUID=E92F51FB4A48E91CE0305794071A149C;CUTYPE=INDIVIDUAL\n ;RSVP=TRUE;CN=James Stevens;PARTSTAT=NEEDS-ACTION:mailto:james.stevens@o\n racle.com\nATTENDEE;X-ORACLE-GUID=E9359406791C763EE0305794071A39A4;CUTYPE=INDIVIDUAL\n ;RSVP=FALSE;CN=Simon Vaillancourt;PARTSTAT=ACCEPTED:mailto:simon.vaillan\n court@oracle.com\nATTENDEE;X-ORACLE-GUID=E9359406791D763EE0305794071A39A4;CUTYPE=INDIVIDUAL\n ;RSVP=TRUE;CN=Bernard Desruisseaux;PARTSTAT=NEEDS-ACTION:mailto:bernard.\n desruisseaux@oracle.com\nATTENDEE;X-ORACLE-GUID=E9359406791E763EE0305794071A39A4;CUTYPE=INDIVIDUAL\n ;RSVP=TRUE;CN=Mario Bonin;PARTSTAT=NEEDS-ACTION:mailto:mario.bonin@oracl\n e.com\nATTENDEE;X-ORACLE-GUID=E9359406791F763EE0305794071A39A4;CUTYPE=INDIVIDUAL\n ;RSVP=TRUE;CN=Jeremy Chone;PARTSTAT=NEEDS-ACTION:mailto:jeremy.chone@ora\n cle.com\nATTENDEE;X-ORACLE-PERSONAL-COMMENT-ISDIRTY=TRUE;X-ORACLE-GUID=E9359406792\n 0763EE0305794071A39A4;CUTYPE=INDIVIDUAL;RSVP=TRUE;CN=Mike Shaver;PARTSTA\n T=NEEDS-ACTION:mailto:mike.x.shaver@oracle.com\nATTENDEE;X-ORACLE-GUID=E93594067921763EE0305794071A39A4;CUTYPE=INDIVIDUAL\n ;RSVP=TRUE;CN=David Ball;PARTSTAT=NEEDS-ACTION:mailto:david.ball@oracle.\n com\nATTENDEE;X-ORACLE-GUID=E93594067922763EE0305794071A39A4;CUTYPE=INDIVIDUAL\n ;RSVP=TRUE;CN=Marten Haring;PARTSTAT=NEEDS-ACTION:mailto:marten.den.hari\n ng@oracle.com\nATTENDEE;X-ORACLE-GUID=E93594067923763EE0305794071A39A4;CUTYPE=INDIVIDUAL\n ;RSVP=TRUE;CN=Peter Egyed;PARTSTAT=NEEDS-ACTION:mailto:peter.egyed@oracl\n e.com\nATTENDEE;X-ORACLE-GUID=E93594067924763EE0305794071A39A4;CUTYPE=INDIVIDUAL\n ;RSVP=TRUE;CN=Francois Perrault;PARTSTAT=NEEDS-ACTION:mailto:francois.pe\n rrault@oracle.com\nATTENDEE;X-ORACLE-GUID=E93594067925763EE0305794071A39A4;CUTYPE=INDIVIDUAL\n ;RSVP=TRUE;CN=Vladimir Vukicevic;PARTSTAT=NEEDS-ACTION:mailto:vladimir.v\n ukicevic@oracle.com\nATTENDEE;X-ORACLE-GUID=E93594067926763EE0305794071A39A4;CUTYPE=INDIVIDUAL\n ;RSVP=TRUE;CN=Cyrus Daboo;PARTSTAT=NEEDS-ACTION:mailto:daboo@isamet.com\nATTENDEE;X-ORACLE-GUID=E93594067927763EE0305794071A39A4;CUTYPE=INDIVIDUAL\n ;RSVP=TRUE;CN=Lisa Dusseault;PARTSTAT=NEEDS-ACTION:mailto:lisa@osafounda\n tion.org\nATTENDEE;X-ORACLE-GUID=E93594067928763EE0305794071A39A4;CUTYPE=INDIVIDUAL\n ;RSVP=TRUE;CN=Dan Mosedale;PARTSTAT=NEEDS-ACTION:mailto:dan.mosedale@ora\n cle.com\nATTENDEE;X-ORACLE-GUID=E93594067929763EE0305794071A39A4;CUTYPE=INDIVIDUAL\n ;RSVP=TRUE;CN=Stuart Parmenter;PARTSTAT=NEEDS-ACTION:mailto:stuart.parme\n nter@oracle.com\nEND:VEVENT\nEND:VCALENDAR\n\n';
-
-dump("* Setting ical string (xmas)\n");
-e.icalString = ics_xmas;
-dump("* Checking basic properties\n");
-var expectedProps =
- [["title", "Christmas"],
- ["id", "20041119T052239Z-1000472-1-5c0746bb-Oracle"],
- ["priority", 0],
- ["status", "CONFIRMED"],
- ["generation", 0],
- ["isAllDay", true]];
-function checkProps(expectedProps, obj) {
- for (var i = 0; i < expectedProps.length; i++) {
- if (obj[expectedProps[i][0]] != expectedProps[i][1]) {
- throw "FAILED! expected " + expectedProps[i][0] + " to be " +
- expectedProps[i][1] + " but got " + obj[expectedProps[i][0]];
- }
- }
-}
-
-checkProps(expectedProps, e);
-
-dump("* Checking start date\n");
-expectedProps =
- [["month", 10],
- ["day", 25],
- ["year", 2004],
- ["isDate", true]];
-checkProps(expectedProps, e.startDate);
-dump("* Checking end date\n");
-checkProps(expectedProps, e.endDate);
-
-dump("PASSED!\n");
deleted file mode 100644
--- a/calendar/test/homegrown/misc/simple-item-mutability.js
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// simple test bits
-//
-
-
-const eventContractID = "@mozilla.org/calendar/event;1";
-const eventIID = Components.interfaces.calIEvent;
-
-dump ("Creating mutable event...\n");
-var me = Components.classes[eventContractID].createInstance(eventIID);
-
-me.title = "Test Title";
-dump ("Title is: " + me.title + " (jsobj: " + me.wrappedJSObject.mTitle + ")\n");
-if (me.title != "Test Title") {
- throw("FAILED! Title on mutable event contained unexpected value.");
-}
-
-dump ("Setting event-to be non-mutable...\n");
-me.makeImmutable();
-
-dump ("Trying to set title (should fail)\n");
-try {
- // this should cause an exception
- me.title = "Fail";
-
- // so we'll only get here if something is wrong, and there's no exception
- dump("FAILED\n!");
-} catch (ex) {
-}
-
-if (me.title != "Test Title") {
- throw("FAILED! Title on immutable event contained unexpected value: "
- + me.title);
-}
-
deleted file mode 100644
--- a/calendar/test/homegrown/misc/synchronization.js
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * This is the memory hog version. Read all events in memory, then sync
- * from there.
- * Doing it the right async way gives me too many headaches.
- */
-
-const calICalendar = Components.interfaces.calICalendar;
-
-var emptyListener =
-{
- onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDetail) {},
- onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {}
-};
-
-var localcal = Components.classes["@mozilla.org/calendar/calendar;1?type=memory"]
- .createInstance(Components.interfaces.calICalendar);
-var remotecal = Components.classes["@mozilla.org/calendar/calendar;1?type=memory"]
- .createInstance(Components.interfaces.calICalendar);
-var changecal = Components.classes["@mozilla.org/calendar/calendar;1?type=memory"]
- .createInstance(Components.interfaces.calICalendar);
-
-
-// local: B CC
-// change: A, B, C
-// remote: A, C
-// expected: B, CC
-createEvent("i2", "B", localcal);
-createEvent("i3", "CC", localcal);
-createEvent("i1", "A", remotecal);
-createEvent("i3", "C", remotecal);
-createEvent("i1", "A", changecal);
-createEvent("i2", "B", changecal);
-createEvent("i3", "C", changecal);
-
-dump("Before\n");
-dumpCals();
-
-// Start by getting all the items into an array
-var localItems = [];
-var remoteItems = [];
-var changeItems = [];
-
-// This will get the items from the calendars into the global arrays,
-// then call doSync()
-getCalendarItems();
-
-function doSync()
-{
- /*
- * forall changeEvents
- * if (!remoteEvent)
- * remoteCalendar.add(localEvent);
- * else if (remoteEvent != changedEvent)
- * conflict();
- * else if (!localEvent)
- * remoteCalendar.deleteEvent(changedEvent.id)
- */
-
- for (id in changeItems) {
- if (!remoteItems[id])
- remotecal.addItem(localItems[id].clone(), emptyListener);
- else if (!itemsAreEqual(remoteItems[id], changeItems[id]))
- dump("Conflict! wheep! "+remoteItems[id].title+" != "+changeItems[id].title+"\n");
- else if (!localItems[id]) {
- remotecal.deleteItem(remoteItems[id], emptyListener);
- delete remoteItems[id];
- }
- }
-
- //dump("Halfway\n");
- //dumpCals();
-
- // now for part two: remote to local
-
- /*
- * forall remoteEvents
- * if (!localEvent)
- * localCalendar.add(remoteEvent);
- * else if (localEvent != remoteEvent)
- * if (!changeEvent)
- * localCalendar.modify(localEvent.id, remoteEvent);
- * else if (changeEvent == remoteEvent)
- * remoteCalendar.modify(remoteEvent.id, localEvent);
- * else
- * conflict();
- */
-
- for (id in remoteItems) {
- if (!localItems[id])
- localcal.addItem(remoteItems[i].clone(), emptyListener);
- if (!itemsAreEqual(localItems[id], remoteItems[id])) {
- if (!changeItems[id]) {
- copyItems(remoteItems[id], localItems[id]);
- localcal.modifyItem(localItems[id], emptyListener);
- } else if (itemsAreEqual(remoteItems[id], changeItems[id])) {
- copyItems(localItems[id], remoteItems[id]);
- remotecal.modifyItem(remoteItems[id], emptyListener);
- } else {
- // There is a conflict, but that was already detected before when
- // dealing with locally changed items. No need to do that again.
- //dump("Conflict\n");
- }
-
- }
- }
-
- dump("After\n");
- dumpCals();
-}
-
-
-function itemsAreEqual(itemA, itemB)
-{
- return (itemA.id == itemB.id && itemA.title == itemB.title)
-}
-
-function copyItems(aSource, aDest)
-{
- aDest.title = aSource.title;
-}
-
-function createEvent(aID, aTitle, aCal)
-{
- var event = Components.classes["@mozilla.org/calendar/event;1"]
- .createInstance(Components.interfaces.calIEvent);
- event.title = aTitle;
- event.id = aID;
- aCal.addItem(event, emptyListener);
-}
-
-function getCalendarItems()
-{
- var calendarsFinished = 0;
-
- var getListener =
- {
- onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDetail)
- {
- calendarsFinished++;
- if (calendarsFinished == 3) {
- doSync();
- }
- },
- onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems)
- {
- if (aCount) {
- var ar;
- if (aCalendar == localcal)
- items = localItems;
- else if (aCalendar == remotecal)
- items = remoteItems;
- else if (aCalendar == changecal)
- items = changeItems;
-
- for (var i=0; i<aCount; ++i) {
- items[aItems[i].id] = aItems[i];
- }
- }
- }
- };
-
- localcal.getItems(calICalendar.ITEM_FILTER_TYPE_ALL | calICalendar.ITEM_FILTER_COMPLETED_ALL,
- 0, null, null, getListener);
- remotecal.getItems(calICalendar.ITEM_FILTER_TYPE_ALL | calICalendar.ITEM_FILTER_COMPLETED_ALL,
- 0, null, null, getListener);
- changecal.getItems(calICalendar.ITEM_FILTER_TYPE_ALL | calICalendar.ITEM_FILTER_COMPLETED_ALL,
- 0, null, null, getListener);
-}
-
-
-function dumpCals()
-{
- var dumpListener =
- {
- onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDetail) {},
- onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems)
- {
- for (var i=0; i<aCount; ++i) {
- dump(" "+i+" "+aItems[i].id+" "+aItems[i].title+"\n");
- }
- }
- };
-
- dump(" Local: \n");
- localcal.getItems(calICalendar.ITEM_FILTER_TYPE_ALL | calICalendar.ITEM_FILTER_COMPLETED_ALL,
- 0, null, null, dumpListener);
- dump("\n Remote: \n");
- remotecal.getItems(calICalendar.ITEM_FILTER_TYPE_ALL | calICalendar.ITEM_FILTER_COMPLETED_ALL,
- 0, null, null, dumpListener);
- dump("\n");
-}
-
deleted file mode 100644
--- a/calendar/test/homegrown/shell.js
+++ /dev/null
@@ -1,239 +0,0 @@
-/* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org calendar code.
- *
- * The Initial Developer of the Original Code is Oracle Corporation
- * Portions created by the Initial Developer are Copyright (C) 2004
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s): Dan Mosedale <dan.mosedale@oracle.com>
- * Mike Shaver <mike.x.shaver@oracle.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-/*
- * This file is intended to allow for an easy synchronous interface to
- * the calendar methods, both for writing tests simply, and for typing at
- * a shell by hand to experiment with calendar methods. "make calshell" in
- * mozilla/calendar/test (or the equivalent objdir) will start a shell with
- * this code pre-loaded.
- */
-
-const C = Components;
-const Ci = C.interfaces;
-const CC = C.classes;
-
-const evQSvc = getService("@mozilla.org/event-queue-service;1",
- "nsIEventQueueService");
-const evQ = evQSvc.getSpecialEventQueue(Ci.nsIEventQueueService.CURRENT_THREAD_EVENT_QUEUE);
-
-// this is necessary so that the event pump isn't used with providers which
-// call the listener before returning, since it would run forever in that case.
-var done = false;
-
-function runEventPump()
-{
- if (done) { // XXX needed?
- done = false;
- return;
- }
- pumpRunning = true;
- while (pumpRunning) {
- evQ.processPendingEvents();
- }
- done = false; // XXX needed?
- return;
-}
-
-function stopEventPump()
-{
- pumpRunning = false;
-}
-
-// I wonder how many copies of this are floating around
-function findErr(result)
-{
- for (var i in C.results) {
- if (C.results[i] == result) {
- return i;
- }
- }
- dump("No result code found for " + result + "\n");
-}
-// I wonder how many copies of this are floating around
-function findIface(iface)
-{
- for (var i in Ci) {
- if (iface.equals(Ci[i])) {
- return i;
- }
- }
- dump("No interface found for " + iface + "\n");
-}
-
-function findOpName(op)
-{
- for (var i in Ci.calIOperationListener) {
- if (op == Ci.calIOperationListener[i]) {
- return i;
- }
- }
- dump("Operation type " + op + "unknown\n");
-}
-
-function getService(contract, iface)
-{
- return C.classes[contract].getService(Ci[iface]);
-}
-
-function createInstance(contract, iface)
-{
- return C.classes[contract].createInstance(Ci[iface]);
-}
-
-function calOpListener() {
-}
-
-calOpListener.prototype =
-{
- mItems: [],
- mDetail: null,
- mId: null,
- mStatus: null,
-
- onOperationComplete: function(aCalendar, aStatus, aOperationType, aId,
- aDetail) {
- stopEventPump();
-
- this.mDetail = aDetail;
- this.mStatus = aStatus;
- this.mId = aId;
-
- // XXX verify aCalendar == cal
-
- done = true;
- return;
- },
-
- onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount,
- aItems) {
-
- // XXX check success(?); dump un-returned data,
-
- this.mItems = aItems;
-
- return;
- }
-}
-
-const ioSvc = getService("@mozilla.org/network/io-service;1", "nsIIOService");
-
-function URLFromSpec(spec)
-{
- return ioSvc.newURI(spec, null, null);
-}
-
-/**
- * convenience var/method for setting up the global calendarn "cal"
- */
-var cal;
-function createCal(type, uri) {
- cal = createInstance("@mozilla.org/calendar/calendar;1?type=" + type,
- Ci.calICalendar);
-
- // if a uri has been specified, set it
- if (uri) {
- cal.uri = URLFromSpec(calendarUri);
- }
-
- return;
-}
-
-/**
- * convenience method to create an item and potentially initialize it from ICS
- */
-function createItem(icalString) {
-
- var item = createInstance("@mozilla.org/calendar/event;1", Ci.calIEvent);
- if (icalString) {
- item.icalString = "icalString";
- }
-
- return item;
-}
-
-/**
- * convenience wrappers around various calICalendar methods so that shell
- * callers don't have to deal with the listener
- */
-function addItem(item) {
- done = false;
- var listener = new calOpListener();
- cal.addItem(item, listener);
- runEventPump();
-
- if (!C.isSuccessCode(listener.mStatus)) {
- throw new Exception(listener.mStatus, listener.mDetail);
- }
- return listener.mDetail.QueryInterface(Ci.calIItemBase);
-}
-
-function getItem(id) {
- done = false;
- var listener = new calOpListener();
- cal.getItem(id, listener);
- runEventPump();
-
- if (!C.isSuccessCode(listener.mStatus)) {
- throw new Exception(listener.mStatus, listener.mDetail);
- }
- return listener.mItems[0];
-}
-
-function getItems(filter, count, rangeStart, rangeEnd) {
- done = false;
- var listener = new calOpListener();
- cal.getItems(filter, count, rangeStart, rangeEnd, listener);
- runEventPump();
-
- if (!C.isSuccessCode(listener.mStatus)) {
- throw new Exception(listener.mStatus, listener.mDetail);
- }
- return listener.mItems;
-}
-
-function deleteItem(item) {
- done = false;
- var listener = new calOpListener();
- cal.deleteItem(item, listener);
- runEventPump();
-
- if (!C.isSuccessCode(listener.mStatus)) {
- throw new Exception(listener.mStatus, listener.mDetail);
- }
- return listener.mId;
-}
deleted file mode 100755
--- a/calendar/test/jsDriver.pl
+++ /dev/null
@@ -1,1295 +0,0 @@
-#!/usr/bin/perl
-#
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is JavaScript Core Tests.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 1997-1999
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# Robert Ginda <rginda@netscape.com>
-# Second cut at runtests.pl script originally by
-# Christine Begle (cbegle@netscape.com)
-# Branched 11/01/99
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
-
-use strict;
-use Getopt::Mixed "nextOption";
-
-my $os_type = &get_os_type;
-my $unixish = (($os_type ne "WIN") && ($os_type ne "MAC"));
-my $path_sep = ($os_type eq "MAC") ? ":" : "/";
-my $win_sep = ($os_type eq "WIN")? &get_win_sep : "";
-my $redirect_command = ($os_type ne "MAC") ? " 2>&1" : "";
-
-# command line option defaults
-my $opt_suite_path;
-my $opt_trace = 0;
-my $opt_classpath = "";
-my $opt_rhino_opt = 0;
-my $opt_rhino_ms = 0;
-my @opt_engine_list;
-my $opt_engine_type = "";
-my $opt_engine_params = "";
-my $opt_user_output_file = 0;
-my $opt_output_file = "";
-my @opt_test_list_files;
-my @opt_neg_list_files;
-my $opt_shell_path = "";
-my $opt_java_path = "";
-my $opt_bug_url = "http://bugzilla.mozilla.org/show_bug.cgi?id=";
-my $opt_console_failures = 0;
-my $opt_lxr_url = "http://lxr.mozilla.org/mozilla/source/js/tests/";
-my $opt_exit_munge = ($os_type ne "MAC") ? 1 : 0;
-
-# command line option definition
-my $options = "b=s bugurl>b c=s classpath>c e=s engine>e f=s file>f " .
- "h help>h i j=s javapath>j k confail>k l=s list>l L=s neglist>L " .
- "o=s opt>o p=s testpath>p s=s shellpath>s t trace>t u=s lxrurl>u " .
- "x noexitmunge>x";
-
-if ($os_type eq "MAC") {
- $opt_suite_path = `directory`;
- $opt_suite_path =~ s/[\n\r]//g;
- $opt_suite_path .= ":";
-} else {
- $opt_suite_path = "./";
-}
-
-&parse_args;
-
-my $user_exit = 0;
-my ($engine_command, $html, $failures_reported, $tests_completed,
- $exec_time_string);
-my @failed_tests;
-my @test_list = &get_test_list;
-
-if ($#test_list == -1) {
- die ("Nothing to test.\n");
-}
-
-if ($unixish) {
- # on unix, ^C pauses the tests, and gives the user a chance to quit but
- # report on what has been done, to just quit, or to continue (the
- # interrupted test will still be skipped.)
- # windows doesn't handle the int handler they way we want it to,
- # so don't even pretend to let the user continue.
- $SIG{INT} = 'int_handler';
-}
-
-&main;
-
-#End.
-
-sub main {
- my $start_time;
-
- while ($opt_engine_type = pop (@opt_engine_list)) {
- dd ("Testing engine '$opt_engine_type'");
-
- $engine_command = &get_engine_command;
- $html = "";
- @failed_tests = ();
- $failures_reported = 0;
- $tests_completed = 0;
- $start_time = time;
-
-
- &execute_tests (@test_list);
-
- my $exec_time = (time - $start_time);
- my $exec_hours = int($exec_time / 60 / 60);
- $exec_time -= $exec_hours * 60 * 60;
- my $exec_mins = int($exec_time / 60);
- $exec_time -= $exec_mins * 60;
- my $exec_secs = ($exec_time % 60);
-
- if ($exec_hours > 0) {
- $exec_time_string = "$exec_hours hours, $exec_mins minutes, " .
- "$exec_secs seconds";
- } elsif ($exec_mins > 0) {
- $exec_time_string = "$exec_mins minutes, $exec_secs seconds";
- } else {
- $exec_time_string = "$exec_secs seconds";
- }
-
- if (!$opt_user_output_file) {
- $opt_output_file = &get_tempfile_name;
- }
-
- &write_results;
-
- }
-}
-
-sub execute_tests {
- my (@test_list) = @_;
- my ($test, $shell_command, $line, @output, $path);
- my $file_param = " -f ";
- my ($last_suite, $last_test_dir);
-
- &status ("Executing " . ($#test_list + 1) . " test(s).");
-
- foreach $test (@test_list) {
- my ($suite, $test_dir, $test_file) = split($path_sep, $test);
- # *-n.js is a negative test, expect exit code 3 (runtime error)
- my $expected_exit = ($test =~ /\-n\.js$/) ? 3 : 0;
- my ($got_exit, $exit_signal);
- my $failure_lines;
- my $bug_number;
- my $status_lines;
-
- # user selected [Q]uit from ^C handler.
- if ($user_exit) {
- return;
- }
-
- # Append the shell.js files to the shell_command if they're there.
- # (only check for their existance if the suite or test_dir has changed
- # since the last time we looked.)
- if ($last_suite ne $suite || $last_test_dir ne $test_dir) {
- $shell_command = &xp_path($engine_command);
-
- $path = &xp_path($opt_suite_path . $suite . "/shell.js");
- if (-f $path) {
- $shell_command .= $file_param . $path;
- }
-
- $path = &xp_path($opt_suite_path . $suite . "/" .
- $test_dir . "/shell.js");
- if (-f $path) {
- $shell_command .= $file_param . $path;
- }
-
- $last_suite = $suite;
- $last_test_dir = $test_dir;
- }
-
- $path = &xp_path($opt_suite_path . $test);
- &dd ("executing: " . $shell_command . $file_param . $path);
-
- open (OUTPUT, $shell_command . $file_param . $path .
- $redirect_command . " |");
- @output = <OUTPUT>;
- close (OUTPUT);
-
- @output = grep (!/js\>/, @output);
-
- if ($opt_exit_munge == 1) {
- # signal information in the lower 8 bits, exit code above that
- $got_exit = ($? >> 8);
- $exit_signal = ($? & 255);
- } else {
- # user says not to munge the exit code
- $got_exit = $?;
- $exit_signal = 0;
- }
-
- $failure_lines = "";
- $bug_number = "";
- $status_lines = "";
-
- foreach $line (@output) {
-
- # watch for testcase to proclaim what exit code it expects to
- # produce (0 by default)
- if ($line =~ /expect(ed)?\s*exit\s*code\s*\:?\s*(\d+)/i) {
- $expected_exit = $2;
- &dd ("Test case expects exit code $expected_exit");
- }
-
- # watch for failures
- if ($line =~ /failed!/i) {
- $failure_lines .= $line;
- }
-
- # and watch for bugnumbers
- # XXX This only allows 1 bugnumber per testfile, should be
- # XXX modified to allow for multiple.
- if ($line =~ /bugnumber\s*\:?\s*(.*)/i) {
- $1 =~ /(\n+)/;
- $bug_number = $1;
- }
-
- # and watch for status
- if ($line =~ /status/i) {
- $status_lines .= $line;
- }
-
- }
-
- if (!@output) {
- @output = ("Testcase produced no output!");
- }
-
- if ($got_exit != $expected_exit) {
- # full testcase output dumped on mismatched exit codes,
- &report_failure ($test, "Expected exit code " .
- "$expected_exit, got $got_exit\n" .
- "Testcase terminated with signal $exit_signal\n" .
- "Complete testcase output was:\n" .
- join ("\n",@output), $bug_number);
- } elsif ($failure_lines) {
- # only offending lines if exit codes matched
- &report_failure ($test, "$status_lines\n".
- "Failure messages were:\n$failure_lines",
- $bug_number);
- }
-
- &dd ("exit code $got_exit, exit signal $exit_signal.");
-
- $tests_completed++;
- }
-}
-
-sub write_results {
- my ($list_name, $neglist_name);
- my $completion_date = localtime;
- my $failure_pct = int(($failures_reported / $tests_completed) * 10000) /
- 100;
- &dd ("Writing output to $opt_output_file.");
-
- if ($#opt_test_list_files == -1) {
- $list_name = "All tests";
- } elsif ($#opt_test_list_files < 10) {
- $list_name = join (", ", @opt_test_list_files);
- } else {
- $list_name = "($#opt_test_list_files test files specified)";
- }
-
- if ($#opt_neg_list_files == -1) {
- $neglist_name = "(none)";
- } elsif ($#opt_test_list_files < 10) {
- $neglist_name = join (", ", @opt_neg_list_files);
- } else {
- $neglist_name = "($#opt_neg_list_files skip files specified)";
- }
-
- open (OUTPUT, "> $opt_output_file") ||
- die ("Could not create output file $opt_output_file");
-
- print OUTPUT
- ("<html><head>\n" .
- "<title>Test results, $opt_engine_type</title>\n" .
- "</head>\n" .
- "<body bgcolor='white'>\n" .
- "<a name='tippy_top'></a>\n" .
- "<h2>Test results, $opt_engine_type</h2><br>\n" .
- "<p class='results_summary'>\n" .
- "Test List: $list_name<br>\n" .
- "Skip List: $neglist_name<br>\n" .
- ($#test_list + 1) . " test(s) selected, $tests_completed test(s) " .
- "completed, $failures_reported failures reported " .
- "($failure_pct% failed)<br>\n" .
- "Engine command line: $engine_command<br>\n" .
- "OS type: $os_type<br>\n");
-
- if ($opt_engine_type =~ /^rhino/) {
- open (JAVAOUTPUT, $opt_java_path . "java -fullversion " .
- $redirect_command . " |");
- print OUTPUT <JAVAOUTPUT>;
- print OUTPUT "<BR>";
- close (JAVAOUTPUT);
- }
-
- print OUTPUT
- ("Testcase execution time: $exec_time_string.<br>\n" .
- "Tests completed on $completion_date.<br><br>\n");
-
- if ($failures_reported > 0) {
- print OUTPUT
- ("[ <a href='#fail_detail'>Failure Details</a> | " .
- "<a href='#retest_list'>Retest List</a> | " .
- "<a href='menu.html'>Test Selection Page</a> ]<br>\n" .
- "<hr>\n" .
- "<a name='fail_detail'></a>\n" .
- "<h2>Failure Details</h2><br>\n<dl>" .
- $html .
- "</dl>\n[ <a href='#tippy_top'>Top of Page</a> | " .
- "<a href='#fail_detail'>Top of Failures</a> ]<br>\n" .
- "<hr>\n" .
- "<a name='retest_list'></a>\n" .
- "<h2>Retest List</h2><br>\n" .
- "<pre>\n" .
- "# Retest List, $opt_engine_type, " .
- "generated $completion_date.\n" .
- "# Original test base was: $list_name.\n" .
- "# $tests_completed of " . ($#test_list + 1) .
- " test(s) were completed, " .
- "$failures_reported failures reported.\n" .
- join ("\n", @failed_tests) .
- "</pre>\n" .
- "[ <a href='#tippy_top'>Top of Page</a> | " .
- "<a href='#retest_list'>Top of Retest List</a> ]<br>\n");
- } else {
- print OUTPUT
- ("<h1>Whoop-de-doo, nothing failed!</h1>\n");
- }
-
- print OUTPUT "</body>";
-
- close (OUTPUT);
-
- &status ("Wrote results to '$opt_output_file'.");
-
- if ($opt_console_failures) {
- &status ("$failures_reported test(s) failed");
- }
-
-}
-
-sub parse_args {
- my ($option, $value, $lastopt);
-
- &dd ("checking command line options.");
-
- Getopt::Mixed::init ($options);
- $Getopt::Mixed::order = $Getopt::Mixed::RETURN_IN_ORDER;
-
- while (($option, $value) = nextOption()) {
-
- if ($option eq "b") {
- &dd ("opt: setting bugurl to '$value'.");
- $opt_bug_url = $value;
-
- } elsif ($option eq "c") {
- &dd ("opt: setting classpath to '$value'.");
- $opt_classpath = $value;
-
- } elsif (($option eq "e") || (($option eq "") && ($lastopt eq "e"))) {
- &dd ("opt: adding engine $value.");
- push (@opt_engine_list, $value);
-
- } elsif ($option eq "f") {
- if (!$value) {
- die ("Output file cannot be null.\n");
- }
- &dd ("opt: setting output file to '$value'.");
- $opt_user_output_file = 1;
- $opt_output_file = $value;
-
- } elsif ($option eq "h") {
- &usage;
-
- } elsif ($option eq "j") {
- if (!($value =~ /[\/\\]$/)) {
- $value .= "/";
- }
- &dd ("opt: setting java path to '$value'.");
- $opt_java_path = $value;
-
- } elsif ($option eq "k") {
- &dd ("opt: displaying failures on console.");
- $opt_console_failures=1;
-
- } elsif ($option eq "l" || (($option eq "") && ($lastopt eq "l"))) {
- $option = "l";
- &dd ("opt: adding test list '$value'.");
- push (@opt_test_list_files, $value);
-
- } elsif ($option eq "L" || (($option eq "") && ($lastopt eq "L"))) {
- $option = "L";
- &dd ("opt: adding negative list '$value'.");
- push (@opt_neg_list_files, $value);
-
- } elsif ($option eq "o") {
- $opt_engine_params = $value;
- &dd ("opt: setting engine params to '$opt_engine_params'.");
-
- } elsif ($option eq "p") {
- $opt_suite_path = $value;
-
- if ($os_type eq "MAC") {
- if (!($opt_suite_path =~ /\:$/)) {
- $opt_suite_path .= ":";
- }
- } else {
- if (!($opt_suite_path =~ /[\/\\]$/)) {
- $opt_suite_path .= "/";
- }
- }
-
- &dd ("opt: setting suite path to '$opt_suite_path'.");
-
- } elsif ($option eq "s") {
- $opt_shell_path = $value;
- &dd ("opt: setting shell path to '$opt_shell_path'.");
-
- } elsif ($option eq "t") {
- &dd ("opt: tracing output. (console failures at no extra charge.)");
- $opt_console_failures = 1;
- $opt_trace = 1;
-
- } elsif ($option eq "u") {
- &dd ("opt: setting lxr url to '$value'.");
- $opt_lxr_url = $value;
-
- } elsif ($option eq "x") {
- &dd ("opt: turning off exit munging.");
- $opt_exit_munge = 0;
-
- } else {
- &usage;
- }
-
- $lastopt = $option;
-
- }
-
- Getopt::Mixed::cleanup();
-
- if ($#opt_engine_list == -1) {
- die "You must select a shell to test in.\n";
- }
-
-}
-
-#
-# print the arguments that this script expects
-#
-sub usage {
- print STDERR
- ("\nusage: $0 [<options>] \n" .
- "(-b|--bugurl) Bugzilla URL.\n" .
- " (default is $opt_bug_url)\n" .
- "(-c|--classpath) Classpath (Rhino only.)\n" .
- "(-e|--engine) <type> ... Specify the type of engine(s) to test.\n" .
- " <type> is one or more of\n" .
- " (smopt|smdebug|lcopt|lcdebug|xpcshell|" .
- "rhino|rhinoi|rhinoms|rhinomsi|rhino9|rhinoms9).\n" .
- "(-f|--file) <file> Redirect output to file named <file>.\n" .
- " (default is " .
- "results-<engine-type>-<date-stamp>.html)\n" .
- "(-h|--help) Print this message.\n" .
- "(-j|--javapath) Location of java executable.\n" .
- "(-k|--confail) Log failures to console (also.)\n" .
- "(-l|--list) <file> ... List of tests to execute.\n" .
- "(-L|--neglist) <file> ... List of tests to skip.\n" .
- "(-o|--opt) <options> Options to pass to the JavaScript engine.\n" .
- " (Make sure to quote them!)\n" .
- "(-p|--testpath) <path> Root of the test suite. (default is ./)\n" .
- "(-s|--shellpath) <path> Location of JavaScript shell.\n" .
- "(-t|--trace) Trace script execution.\n" .
- "(-u|--lxrurl) <url> Complete URL to tests subdirectory on lxr.\n" .
- " (default is $opt_lxr_url)\n" .
- "(-x|--noexitmunge) Don't do exit code munging (try this if it\n" .
- " seems like your exit codes are turning up\n" .
- " as exit signals.)\n");
- exit (1);
-
-}
-
-#
-# get the shell command used to start the (either) engine
-#
-sub get_engine_command {
-
- my $retval;
-
- if ($opt_engine_type eq "rhino") {
- &dd ("getting rhino engine command.");
- $opt_rhino_opt = 0;
- $opt_rhino_ms = 0;
- $retval = &get_rhino_engine_command;
- } elsif ($opt_engine_type eq "rhinoi") {
- &dd ("getting rhinoi engine command.");
- $opt_rhino_opt = -1;
- $opt_rhino_ms = 0;
- $retval = &get_rhino_engine_command;
- } elsif ($opt_engine_type eq "rhino9") {
- &dd ("getting rhino engine command.");
- $opt_rhino_opt = 9;
- $opt_rhino_ms = 0;
- $retval = &get_rhino_engine_command;
- } elsif ($opt_engine_type eq "rhinoms") {
- &dd ("getting rhinoms engine command.");
- $opt_rhino_opt = 0;
- $opt_rhino_ms = 1;
- $retval = &get_rhino_engine_command;
- } elsif ($opt_engine_type eq "rhinomsi") {
- &dd ("getting rhinomsi engine command.");
- $opt_rhino_opt = -1;
- $opt_rhino_ms = 1;
- $retval = &get_rhino_engine_command;
- } elsif ($opt_engine_type eq "rhinoms9") {
- &dd ("getting rhinomsi engine command.");
- $opt_rhino_opt = 9;
- $opt_rhino_ms = 1;
- $retval = &get_rhino_engine_command;
- } elsif ($opt_engine_type eq "xpcshell") {
- &dd ("getting xpcshell engine command.");
- $retval = &get_xpc_engine_command;
- } elsif ($opt_engine_type =~ /^lc(opt|debug)$/) {
- &dd ("getting liveconnect engine command.");
- $retval = &get_lc_engine_command;
- } elsif ($opt_engine_type =~ /^sm(opt|debug)$/) {
- &dd ("getting spidermonkey engine command.");
- $retval = &get_sm_engine_command;
- } elsif ($opt_engine_type =~ /^ep(opt|debug)$/) {
- &dd ("getting epimetheus engine command.");
- $retval = &get_ep_engine_command;
- } else {
- die ("Unknown engine type selected, '$opt_engine_type'.\n");
- }
-
- $retval .= " $opt_engine_params";
-
- &dd ("got '$retval'");
-
- return $retval;
-
-}
-
-#
-# get the shell command used to run rhino
-#
-sub get_rhino_engine_command {
- my $retval = $opt_java_path . ($opt_rhino_ms ? "jview " : "java ");
-
- if ($opt_shell_path) {
- $opt_classpath = ($opt_classpath) ?
- $opt_classpath . ":" . $opt_shell_path :
- $opt_shell_path;
- }
-
- if ($opt_classpath) {
- $retval .= ($opt_rhino_ms ? "/cp:p" : "-classpath") . " $opt_classpath ";
- }
-
- $retval .= "org.mozilla.javascript.tools.shell.Main";
-
- if ($opt_rhino_opt) {
- $retval .= " -opt $opt_rhino_opt";
- }
-
- return $retval;
-
-}
-
-#
-# get the shell command used to run xpcshell
-#
-sub get_xpc_engine_command {
- my $retval;
- my $m5_home = @ENV{"MOZILLA_FIVE_HOME"} ||
- die ("You must set MOZILLA_FIVE_HOME to use the xpcshell" ,
- (!$unixish) ? "." : ", also " .
- "setting LD_LIBRARY_PATH to the same directory may get rid of " .
- "any 'library not found' errors.\n");
-
- if (($unixish) && (!@ENV{"LD_LIBRARY_PATH"})) {
- print STDERR "-#- WARNING: LD_LIBRARY_PATH is not set, xpcshell may " .
- "not be able to find the required components.\n";
- }
-
- if (!($m5_home =~ /[\/\\]$/)) {
- $m5_home .= "/";
- }
-
- $retval = $m5_home . "xpcshell";
-
- if ($os_type eq "WIN") {
- $retval .= ".exe";
- }
-
- $retval = &xp_path($retval);
-
- if (($os_type ne "MAC") && !(-x $retval)) {
- # mac doesn't seem to deal with -x correctly
- die ($retval . " is not a valid executable on this system.\n");
- }
-
- return $retval;
-
-}
-
-#
-# get the shell command used to run spidermonkey
-#
-sub get_sm_engine_command {
- my $retval;
-
- # Look for Makefile.ref style make first.
- # (On Windows, spidermonkey can be made by two makefiles, each putting the
- # executable in a diferent directory, under a different name.)
-
- if ($opt_shell_path) {
- # if the user provided a path to the shell, return that.
- $retval = $opt_shell_path;
-
- } else {
-
- if ($os_type eq "MAC") {
- $retval = $opt_suite_path . ":src:macbuild:JS";
- } else {
- $retval = $opt_suite_path . "../src/";
- opendir (SRC_DIR_FILES, $retval);
- my @src_dir_files = readdir(SRC_DIR_FILES);
- closedir (SRC_DIR_FILES);
-
- my ($dir, $object_dir);
- my $pattern = ($opt_engine_type eq "smdebug") ?
- 'DBG.OBJ' : 'OPT.OBJ';
-
- # scan for the first directory matching
- # the pattern expected to hold this type (debug or opt) of engine
- foreach $dir (@src_dir_files) {
- if ($dir =~ $pattern) {
- $object_dir = $dir;
- last;
- }
- }
-
- if (!$object_dir && $os_type ne "WIN") {
- die ("Could not locate an object directory in $retval " .
- "matching the pattern *$pattern. Have you built the " .
- "engine?\n");
- }
-
- if (!(-x $retval . $object_dir . "/js.exe") && ($os_type eq "WIN")) {
- # On windows, you can build with js.mak as well as Makefile.ref
- # (Can you say WTF boys and girls? I knew you could.)
- # So, if the exe the would have been built by Makefile.ref isn't
- # here, check for the js.mak version before dying.
- if ($opt_shell_path) {
- $retval = $opt_shell_path;
- if (!($retval =~ /[\/\\]$/)) {
- $retval .= "/";
- }
- } else {
- if ($opt_engine_type eq "smopt") {
- $retval = "../src/Release/";
- } else {
- $retval = "../src/Debug/";
- }
- }
-
- $retval .= "jsshell.exe";
-
- } else {
- $retval .= $object_dir . "/js";
- if ($os_type eq "WIN") {
- $retval .= ".exe";
- }
- }
- } # mac/ not mac
-
- $retval = &xp_path($retval);
-
- } # (user provided a path)
-
-
- if (($os_type ne "MAC") && !(-x $retval)) {
- # mac doesn't seem to deal with -x correctly
- die ($retval . " is not a valid executable on this system.\n");
- }
-
- return $retval;
-
-}
-
-#
-# get the shell command used to run epimetheus
-#
-sub get_ep_engine_command {
- my $retval;
-
- if ($opt_shell_path) {
- # if the user provided a path to the shell, return that -
- $retval = $opt_shell_path;
-
- } else {
- my $dir;
- my $os;
- my $debug;
- my $opt;
- my $exe;
-
- $dir = $opt_suite_path . "../../js2/src/";
-
- if ($os_type eq "MAC") {
- #
- # On the Mac, the debug and opt builds lie in the same directory -
- #
- $os = "macbuild:";
- $debug = "";
- $opt = "";
- $exe = "JS2";
- } elsif ($os_type eq "WIN") {
- $os = "winbuild/Epimetheus/";
- $debug = "Debug/";
- $opt = "Release/";
- $exe = "Epimetheus.exe";
- } else {
- $os = "";
- $debug = "";
- $opt = ""; # <<<----- XXX THIS IS NOT RIGHT! CHANGE IT!
- $exe = "epimetheus";
- }
-
-
- if ($opt_engine_type eq "epdebug") {
- $retval = $dir . $os . $debug . $exe;
- } else {
- $retval = $dir . $os . $opt . $exe;
- }
-
- $retval = &xp_path($retval);
-
- }# (user provided a path)
-
-
- if (($os_type ne "MAC") && !(-x $retval)) {
- # mac doesn't seem to deal with -x correctly
- die ($retval . " is not a valid executable on this system.\n");
- }
-
- return $retval;
-}
-
-#
-# get the shell command used to run the liveconnect shell
-#
-sub get_lc_engine_command {
- my $retval;
-
- if ($opt_shell_path) {
- $retval = $opt_shell_path;
- } else {
- if ($os_type eq "MAC") {
- die "Don't know how to run the lc shell on the mac yet.\n";
- } else {
- $retval = $opt_suite_path . "../src/liveconnect/";
- opendir (SRC_DIR_FILES, $retval);
- my @src_dir_files = readdir(SRC_DIR_FILES);
- closedir (SRC_DIR_FILES);
-
- my ($dir, $object_dir);
- my $pattern = ($opt_engine_type eq "lcdebug") ?
- 'DBG.OBJ' : 'OPT.OBJ';
-
- foreach $dir (@src_dir_files) {
- if ($dir =~ $pattern) {
- $object_dir = $dir;
- last;
- }
- }
-
- if (!$object_dir) {
- die ("Could not locate an object directory in $retval " .
- "matching the pattern *$pattern. Have you built the " .
- "engine?\n");
- }
-
- $retval .= $object_dir . "/";
-
- if ($os_type eq "WIN") {
- $retval .= "lcshell.exe";
- } else {
- $retval .= "lcshell";
- }
- } # mac/ not mac
-
- $retval = &xp_path($retval);
-
- } # (user provided a path)
-
-
- if (($os_type ne "MAC") && !(-x $retval)) {
- # mac doesn't seem to deal with -x correctly
- die ("$retval is not a valid executable on this system.\n");
- }
-
- return $retval;
-
-}
-
-sub get_os_type {
-
- if ("\n" eq "\015") {
- return "MAC";
- }
-
- my $uname = `uname -a`;
-
- if ($uname =~ /WIN/) {
- $uname = "WIN";
- } else {
- chop $uname;
- }
-
- &dd ("get_os_type returning '$uname'.");
- return $uname;
-
-}
-
-sub get_test_list {
- my @test_list;
- my @neg_list;
-
- if ($#opt_test_list_files > -1) {
- my $list_file;
-
- &dd ("getting test list from user specified source.");
-
- foreach $list_file (@opt_test_list_files) {
- push (@test_list, &expand_user_test_list($list_file));
- }
- } else {
- &dd ("no list file, groveling in '$opt_suite_path'.");
-
- @test_list = &get_default_test_list($opt_suite_path);
- }
-
- if ($#opt_neg_list_files > -1) {
- my $list_file;
- my $orig_size = $#test_list + 1;
- my $actually_skipped;
-
- &dd ("getting negative list from user specified source.");
-
- foreach $list_file (@opt_neg_list_files) {
- push (@neg_list, &expand_user_test_list($list_file));
- }
-
- @test_list = &subtract_arrays (\@test_list, \@neg_list);
-
- $actually_skipped = $orig_size - ($#test_list + 1);
-
- &dd ($actually_skipped . " of " . $orig_size .
- " tests will be skipped.");
- &dd ((($#neg_list + 1) - $actually_skipped) . " skip tests were " .
- "not actually part of the test list.");
-
- }
-
-
- # Don't run any shell.js files as tests; they are only utility files
- @test_list = grep (!/shell.*\.js$/, @test_list);
-
- return @test_list;
-
-}
-
-#
-# reads $list_file, storing non-comment lines into an array.
-# lines in the form suite_dir/[*] or suite_dir/test_dir/[*] are expanded
-# to include all test files under the specified directory
-#
-sub expand_user_test_list {
- my ($list_file) = @_;
- my @retval = ();
-
- #
- # Trim off the leading path separator that begins relative paths on the Mac.
- # Each path will get concatenated with $opt_suite_path, which ends in one.
- #
- # Also note:
- #
- # We will call expand_test_list_entry(), which does pattern-matching on $list_file.
- # This will make the pattern-matching the same as it would be on Linux/Windows -
- #
- if ($os_type eq "MAC") {
- $list_file =~ s/^$path_sep//;
- }
-
- if ($list_file =~ /\.js$/ || -d $opt_suite_path . $list_file) {
-
- push (@retval, &expand_test_list_entry($list_file));
-
- } else {
-
- open (TESTLIST, $list_file) ||
- die("Error opening test list file '$list_file': $!\n");
-
- while (<TESTLIST>) {
- s/\r*\n*$//;
- if (!(/\s*\#/)) {
- # It's not a comment, so process it
- push (@retval, &expand_test_list_entry($_));
- }
- }
-
- close (TESTLIST);
-
- }
-
- return @retval;
-
-}
-
-
-#
-# Currently expect all paths to be RELATIVE to the top-level tests directory.
-# One day, this should be improved to allow absolute paths as well -
-#
-sub expand_test_list_entry {
- my ($entry) = @_;
- my @retval;
-
- if ($entry =~ /\.js$/) {
- # it's a regular entry, add it to the list
- if (-f $opt_suite_path . $entry) {
- push (@retval, $entry);
- } else {
- status ("testcase '$entry' not found.");
- }
- } elsif ($entry =~ /(.*$path_sep[^\*][^$path_sep]*)$path_sep?\*?$/) {
- # Entry is in the form suite_dir/test_dir[/*]
- # so iterate all tests under it
- my $suite_and_test_dir = $1;
- my @test_files = &get_js_files ($opt_suite_path .
- $suite_and_test_dir);
- my $i;
-
- foreach $i (0 .. $#test_files) {
- $test_files[$i] = $suite_and_test_dir . $path_sep .
- $test_files[$i];
- }
-
- splice (@retval, $#retval + 1, 0, @test_files);
-
- } elsif ($entry =~ /([^\*][^$path_sep]*)$path_sep?\*?$/) {
- # Entry is in the form suite_dir[/*]
- # so iterate all test dirs and tests under it
- my $suite = $1;
- my @test_dirs = &get_subdirs ($opt_suite_path . $suite);
- my $test_dir;
-
- foreach $test_dir (@test_dirs) {
- my @test_files = &get_js_files ($opt_suite_path . $suite .
- $path_sep . $test_dir);
- my $i;
-
- foreach $i (0 .. $#test_files) {
- $test_files[$i] = $suite . $path_sep . $test_dir . $path_sep .
- $test_files[$i];
- }
-
- splice (@retval, $#retval + 1, 0, @test_files);
- }
-
- } else {
- die ("Dont know what to do with list entry '$entry'.\n");
- }
-
- return @retval;
-
-}
-
-#
-# Grovels through $suite_path, searching for *all* test files. Used when the
-# user doesn't supply a test list.
-#
-sub get_default_test_list {
- my ($suite_path) = @_;
- my @suite_list = &get_subdirs($suite_path);
- my $suite;
- my @retval;
-
- foreach $suite (@suite_list) {
- my @test_dir_list = get_subdirs ($suite_path . $suite);
- my $test_dir;
-
- foreach $test_dir (@test_dir_list) {
- my @test_list = get_js_files ($suite_path . $suite . $path_sep .
- $test_dir);
- my $test;
-
- foreach $test (@test_list) {
- $retval[$#retval + 1] = $suite . $path_sep . $test_dir .
- $path_sep . $test;
- }
- }
- }
-
- return @retval;
-
-}
-
-#
-# generate an output file name based on the date
-#
-sub get_tempfile_name {
- my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
- &get_padded_time (localtime);
- my $rv;
-
- if ($os_type ne "MAC") {
- $rv = "results-" . $year . "-" . $mon . "-" . $mday . "-" . $hour .
- $min . $sec . "-" . $opt_engine_type;
- } else {
- $rv = "res-" . $year . $mon . $mday . $hour . $min . $sec . "-" .
- $opt_engine_type
- }
-
- return $rv . ".html";
-}
-
-sub get_padded_time {
- my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = @_;
-
- $mon++;
- $mon = &zero_pad($mon);
- $year += 1900;
- $mday= &zero_pad($mday);
- $sec = &zero_pad($sec);
- $min = &zero_pad($min);
- $hour = &zero_pad($hour);
-
- return ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst);
-
-}
-
-sub zero_pad {
- my ($string) = @_;
-
- $string = ($string < 10) ? "0" . $string : $string;
- return $string;
-}
-
-sub subtract_arrays {
- my ($whole_ref, $part_ref) = @_;
- my @whole = @$whole_ref;
- my @part = @$part_ref;
- my $line;
-
- foreach $line (@part) {
- @whole = grep (!/$line/, @whole);
- }
-
- return @whole;
-
-}
-
-#
-# Convert unix path to mac style.
-#
-sub unix_to_mac {
- my ($path) = @_;
- my @path_elements = split ("/", $path);
- my $rv = "";
- my $i;
-
- foreach $i (0 .. $#path_elements) {
- if ($path_elements[$i] eq ".") {
- if (!($rv =~ /\:$/)) {
- $rv .= ":";
- }
- } elsif ($path_elements[$i] eq "..") {
- if (!($rv =~ /\:$/)) {
- $rv .= "::";
- } else {
- $rv .= ":";
- }
- } elsif ($path_elements[$i] ne "") {
- $rv .= $path_elements[$i] . ":";
- }
-
- }
-
- $rv =~ s/\:$//;
-
- return $rv;
-}
-
-#
-# Convert unix path to win style.
-#
-sub unix_to_win {
- my ($path) = @_;
-
- if ($path_sep ne $win_sep) {
- $path =~ s/$path_sep/$win_sep/g;
- }
-
- # translate cygwin-style paths, since the shells are not linked with cygwin
- $path =~ s#^/cygdrive/([a-zA-Z])/#\1:/#;
- return $path;
-}
-
-#
-# Windows shells require "/" or "\" as path separator.
-# Find out the one used in the current Windows shell.
-#
-sub get_win_sep {
- my $path = $ENV{"PATH"} || $ENV{"Path"} || $ENV{"path"};
- $path =~ /\\|\//;
- return $&;
-}
-
-#
-# Convert unix path to correct style based on platform.
-#
-sub xp_path {
- my ($path) = @_;
-
- if ($os_type eq "MAC") {
- return &unix_to_mac($path);
- } elsif($os_type eq "WIN") {
- return &unix_to_win($path);
- } else {
- return $path;
- }
-}
-
-#
-# given a directory, return an array of all subdirectories
-#
-sub get_subdirs {
- my ($dir) = @_;
- my @subdirs;
-
- if ($os_type ne "MAC") {
- if (!($dir =~ /\/$/)) {
- $dir = $dir . "/";
- }
- } else {
- if (!($dir =~ /\:$/)) {
- $dir = $dir . ":";
- }
- }
- opendir (DIR, $dir) || die ("couldn't open directory $dir: $!");
- my @testdir_contents = readdir(DIR);
- closedir(DIR);
-
- foreach (@testdir_contents) {
- if ((-d ($dir . $_)) && ($_ ne 'CVS') && ($_ ne '.') && ($_ ne '..')) {
- @subdirs[$#subdirs + 1] = $_;
- }
- }
-
- return @subdirs;
-}
-
-#
-# given a directory, return an array of all the js files that are in it.
-#
-sub get_js_files {
- my ($test_subdir) = @_;
- my (@js_file_array, @subdir_files);
-
- opendir (TEST_SUBDIR, $test_subdir) || die ("couldn't open directory " .
- "$test_subdir: $!");
- @subdir_files = readdir(TEST_SUBDIR);
- closedir( TEST_SUBDIR );
-
- foreach (@subdir_files) {
- if ($_ =~ /\.js$/) {
- $js_file_array[$#js_file_array+1] = $_;
- }
- }
-
- return @js_file_array;
-}
-
-sub report_failure {
- my ($test, $message, $bug_number) = @_;
- my $bug_line = "";
-
- $failures_reported++;
-
- $message =~ s/\n+/\n/g;
- $test =~ s/\:/\//g;
-
- if ($opt_console_failures) {
- if($bug_number) {
- print STDERR ("*-* Testcase $test failed:\nBug Number $bug_number".
- "\n$message\n");
- } else {
- print STDERR ("*-* Testcase $test failed:\n$message\n");
- }
- }
-
- $message =~ s/\n/<br>\n/g;
- $html .= "<a name='failure$failures_reported'></a>";
-
- if ($bug_number) {
- my $bug_url = ($bug_number =~ /^\d+$/) ? "$opt_bug_url$bug_number" : $bug_number;
- $bug_line = "<a href='$bug_url' target='other_window'>".
- "Bug Number $bug_number</a>";
- }
-
- if ($opt_lxr_url) {
- $test =~ /\/?([^\/]+\/[^\/]+\/[^\/]+)$/;
- $test = $1;
- $html .= "<dd><b>".
- "Testcase <a target='other_window' href='$opt_lxr_url$test'>$1</a> " .
- "failed</b> $bug_line<br>\n";
- } else {
- $html .= "<dd><b>".
- "Testcase $test failed</b> $bug_line<br>\n";
- }
-
- $html .= " [ ";
- if ($failures_reported > 1) {
- $html .= "<a href='#failure" . ($failures_reported - 1) . "'>" .
- "Previous Failure</a> | ";
- }
-
- $html .= "<a href='#failure" . ($failures_reported + 1) . "'>" .
- "Next Failure</a> | " .
- "<a href='#tippy_top'>Top of Page</a> ]<br>\n" .
- "<tt>$message</tt><br>\n";
-
- @failed_tests[$#failed_tests + 1] = $test;
-
-}
-
-sub dd {
-
- if ($opt_trace) {
- print ("-*- ", @_ , "\n");
- }
-
-}
-
-sub status {
-
- print ("-#- ", @_ , "\n");
-
-}
-
-sub int_handler {
- my $resp;
-
- do {
- print ("\n*** User Break: Just [Q]uit, Quit and [R]eport, [C]ontinue ?");
- $resp = <STDIN>;
- } until ($resp =~ /[QqRrCc]/);
-
- if ($resp =~ /[Qq]/) {
- print ("User Exit. No results were generated.\n");
- exit;
- } elsif ($resp =~ /[Rr]/) {
- $user_exit = 1;
- }
-
-}
old mode 100644
new mode 100755
--- a/client.py
+++ b/client.py
@@ -1,9 +1,9 @@
-#!/usr/bin/python
+#!/usr/bin/env python
LDAPCSDK_CO_TAG = 'LDAPCSDK_6_0_6B_MOZILLA_RTM'
CHATZILLA_CO_TAG = 'HEAD'
LDAPCSDK_DIRS = ('directory/c-sdk',)
CHATZILLA_DIRS = ('extensions/irc',)
--- a/mail/Makefile.in
+++ b/mail/Makefile.in
@@ -38,17 +38,17 @@
DEPTH = ..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(topsrcdir)/config/config.mk
# app is always last as it packages up the built files on mac
-DIRS = base locales components extensions themes app
+DIRS = base locales components extensions steel themes app
ifeq ($(OS_ARCH),WINNT)
ifdef MOZ_INSTALLER
# though some lasts are more last than others
DIRS += installer/windows
endif
endif
--- a/mail/app/profile/all-thunderbird.js
+++ b/mail/app/profile/all-thunderbird.js
@@ -253,38 +253,16 @@ pref("mail.default_html_action", 3);
/////////////////////////////////////////////////////////////////
// End seamonkey suite mailnews.js pref overrides
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// Overrides for generic app behavior from the seamonkey suite's all.js
/////////////////////////////////////////////////////////////////
-// l12n and i18n
-pref("intl.charsetmenu.mailedit", "chrome://global/locale/intl.properties");
-pref("intl.accept_languages", "chrome://global/locale/intl.properties");
-pref("intl.charsetmenu.browser.static", "chrome://global/locale/intl.properties");
-pref("intl.charsetmenu.browser.more1", "chrome://global/locale/intl.properties");
-pref("intl.charsetmenu.browser.more2", "chrome://global/locale/intl.properties");
-pref("intl.charsetmenu.browser.more3", "chrome://global/locale/intl.properties");
-pref("intl.charsetmenu.browser.more4", "chrome://global/locale/intl.properties");
-pref("intl.charsetmenu.browser.more5", "chrome://global/locale/intl.properties");
-pref("intl.charsetmenu.browser.unicode", "chrome://global/locale/intl.properties");
-pref("intl.charset.detector", "chrome://global/locale/intl.properties");
-pref("intl.charset.default", "chrome://global-platform/locale/intl.properties");
-pref("font.language.group", "chrome://global/locale/intl.properties");
-pref("intl.menuitems.alwaysappendaccesskeys","chrome://global/locale/intl.properties");
-pref("intl.menuitems.insertseparatorbeforeaccesskeys","chrome://global/locale/intl.properties");
-
-pref("signon.rememberSignons", true);
-pref("signon.expireMasterPassword", false);
-pref("signon.SignonFileName", "signons.txt");
-pref("signon.SignonFileName2", "signons2.txt");
-pref("signon.SignonFileName3", "signons3.txt");
-
pref("browser.hiddenWindowChromeURL", "chrome://messenger/content/hiddenWindow.xul");
pref("offline.startup_state", 2);
// 0 Ask before sending unsent messages when going online
// 1 Always send unsent messages when going online
// 2 Never send unsent messages when going online
pref("offline.send.unsent_messages", 0);
--- a/mail/base/content/FilterListDialog.js
+++ b/mail/base/content/FilterListDialog.js
@@ -145,17 +145,17 @@ function CanRunFiltersAfterTheFact(aServ
// roots the tree at the specified folder
function setFolder(msgFolder)
{
if (msgFolder == gCurrentFolder)
return;
//Calling getFilterList will detect any errors in rules.dat, backup the file, and alert the user
- var filterList = msgFolder.getFilterList(gFilterListMsgWindow);
+ var filterList = msgFolder.getEditableFilterList(gFilterListMsgWindow);
rebuildFilterList(filterList);
// Select the first item in the list, if there is one.
var list = document.getElementById("filterList");
if (list.getRowCount())
list.selectItem(list.getItemAtIndex(0));
// root the folder picker to this server
--- a/mail/base/content/SearchDialog.js
+++ b/mail/base/content/SearchDialog.js
@@ -1,10 +1,9 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
@@ -17,49 +16,47 @@
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Håkan Waara <hwaara@chello.se>
+ * Andrew Sutherland <asutherland@asutherland.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-var searchSessionContractID = "@mozilla.org/messenger/searchSession;1";
-var gSearchView;
-var gSearchSession;
var gCurrentFolder;
-var nsIMsgFolder = Components.interfaces.nsIMsgFolder;
+var gFolderDisplay;
+// Although we don't display messages, we have a message display object to
+// simplify our code. It's just always disabled.
+var gMessageDisplay;
+
var nsIMsgWindow = Components.interfaces.nsIMsgWindow;
-var nsIMsgRDFDataSource = Components.interfaces.nsIMsgRDFDataSource;
-var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
-var gFolderDatasource;
var gFolderPicker;
var gStatusFeedback;
var gTimelineEnabled = false;
var gMessengerBundle = null;
var RDF;
var gSearchBundle;
-var gNextMessageViewIndexAfterDelete = -2;
// Datasource search listener -- made global as it has to be registered
// and unregistered in different functions.
var gDataSourceSearchListener;
var gViewSearchListener;
var gSearchStopButton;
@@ -84,34 +81,35 @@ var nsSearchResultsController =
},
// this controller only handles commands
// that rely on items being selected in
// the search results pane.
isCommandEnabled: function(command)
{
var enabled = true;
-
- switch (command) {
+
+ switch (command) {
case "goto_folder_button":
if (GetNumSelectedMessages() != 1)
enabled = false;
break;
case "cmd_delete":
case "cmd_shiftDelete":
case "button_delete":
// this assumes that advanced searches don't cross accounts
- if (GetNumSelectedMessages() <= 0 || isNewsURI(gSearchView.getURIForViewIndex(0)))
+ if (GetNumSelectedMessages() <= 0 ||
+ isNewsURI(gFolderDisplay.view.dbView.getURIForViewIndex(0)))
enabled = false;
break;
case "saveas_vf_button":
// need someway to see if there are any search criteria...
return true;
case "cmd_selectAll":
- return GetDBView() != null;
+ return true;
default:
if (GetNumSelectedMessages() <= 0)
enabled = false;
break;
}
return enabled;
},
@@ -137,230 +135,218 @@ var nsSearchResultsController =
case "saveas_vf_button":
saveAsVirtualFolder();
return true;
case "cmd_selectAll":
// move the focus to the search results pane
GetThreadTree().focus();
- GetDBView().doCommand(nsMsgViewCommandType.selectAll)
+ gFolderDisplay.doCommand(nsMsgViewCommandType.selectAll);
return true;
-
+
default:
return false;
}
},
onEvent: function(event)
{
}
}
function UpdateMailSearch(caller)
{
- //dump("XXX update mail-search " + caller + "\n");
document.commandDispatcher.updateCommands('mail-search');
}
+/**
+ * FolderDisplayWidget currently calls this function when the command updater
+ * notification for updateCommandStatus is called. We don't have a toolbar,
+ * but our 'mail-search' command set serves the same purpose.
+ */
+var UpdateMailToolbar = UpdateMailSearch;
+
+/**
+ * No-op clear message pane function for FolderDisplayWidget.
+ */
+function ClearMessagePane() {
+}
function SetAdvancedSearchStatusText(aNumHits)
{
- var statusMsg;
- // if there are no hits, it means no matches were found in the search.
- if (aNumHits == 0)
- statusMsg = gSearchBundle.getString("searchFailureMessage");
- else
- {
- if (aNumHits == 1)
- statusMsg = gSearchBundle.getString("searchSuccessMessage");
- else
- statusMsg = gSearchBundle.getFormattedString("searchSuccessMessages", [aNumHits]);
- }
-
- gStatusFeedback.showStatusString(statusMsg);
}
-// nsIMsgSearchNotify object
-var gSearchNotificationListener =
-{
- onSearchHit: function(header, folder)
- {
- // XXX TODO
- // update status text?
- },
-
- onSearchDone: function(status)
- {
- gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForSearchButton"));
- gSearchStopButton.setAttribute("accesskey", gSearchBundle.getString("labelForSearchButton.accesskey"));
- gStatusFeedback._stopMeteors();
- SetAdvancedSearchStatusText(gSearchView.QueryInterface(Components.interfaces.nsITreeView).rowCount);
- },
-
- onNewSearch: function()
- {
- gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForStopButton"));
- gSearchStopButton.setAttribute("accesskey", gSearchBundle.getString("labelForStopButton.accesskey"));
- UpdateMailSearch("new-search");
- gStatusFeedback._startMeteors();
- gStatusFeedback.showStatusString(gSearchBundle.getString("searchingMessage"));
- }
+/**
+ * Subclass the FolderDisplayWidget to deal with UI specific to the search
+ * window.
+ */
+function SearchFolderDisplayWidget(aMessageDisplay) {
+ FolderDisplayWidget.call(this, /* no tab info */ null, aMessageDisplay);
}
-// the folderListener object
-var gFolderListener = {
- OnItemAdded: function(parentItem, item) {},
+SearchFolderDisplayWidget.prototype = {
+ __proto__: FolderDisplayWidget.prototype,
+
+ /// folder display will want to show the thread pane; we need do nothing
+ _showThreadPane: function () {},
- OnItemRemoved: function(parentItem, item){},
+ onSearching: function SearchFolderDisplayWidget_onSearch(aIsSearching) {
+ if (aIsSearching) {
+ // Search button becomes the "stop" button
+ gSearchStopButton.setAttribute(
+ "label", gSearchBundle.getString("labelForStopButton"));
+ gSearchStopButton.setAttribute(
+ "accesskey", gSearchBundle.getString("labelForStopButton.accesskey"));
- OnItemPropertyChanged: function(item, property, oldValue, newValue) {},
-
- OnItemIntPropertyChanged: function(item, property, oldValue, newValue) {},
-
- OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) {},
-
- OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue){},
- OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) {},
+ // update our toolbar equivalent
+ UpdateMailSearch("new-search");
+ // spin the meteors
+ gStatusFeedback._startMeteors();
+ // tell the user that we're searching
+ gStatusFeedback.showStatusString(
+ gSearchBundle.getString("searchingMessage"));
+ }
+ else {
+ // Stop button resumes being the "search" button
+ gSearchStopButton.setAttribute(
+ "label", gSearchBundle.getString("labelForSearchButton"));
+ gSearchStopButton.setAttribute(
+ "accesskey", gSearchBundle.getString("labelForSearchButton.accesskey"));
- OnItemEvent: function(folder, event) {
- var eventType = event.toString();
-
- if (eventType == "DeleteOrMoveMsgCompleted") {
- HandleDeleteOrMoveMessageCompleted(folder);
- }
- else if (eventType == "DeleteOrMoveMsgFailed") {
- HandleDeleteOrMoveMessageFailed(folder);
- }
+ // update our toolbar equivalent
+ UpdateMailSearch("done-search");
+ // stop spining the meteors
+ gStatusFeedback._stopMeteors();
+ // set the result test
+ this.updateStatusResultText();
}
-}
+ },
-function HideSearchColumn(id)
-{
- var col = document.getElementById(id);
- if (col) {
- col.setAttribute("hidden","true");
- col.setAttribute("ignoreincolumnpicker","true");
- }
-}
+ /**
+ * If messages were removed, we might have lost some search results and so
+ * should update our search result text. Also, defer to our super-class.
+ */
+ onMessagesRemoved: function SearchFolderDisplayWidget_onMessagesRemoved() {
+ // result text is only for when we are not searching
+ if (!this.view.searching)
+ this.updateStatusResultText();
+ this.__proto__.__proto__.onMessagesRemoved.call(this);
+ },
-function ShowSearchColumn(id)
-{
- var col = document.getElementById(id);
- if (col) {
- col.removeAttribute("hidden");
- col.removeAttribute("ignoreincolumnpicker");
- }
-}
+ updateStatusResultText: function() {
+ let statusMsg, rowCount = this.view.dbView.rowCount;
+ // if there are no hits, it means no matches were found in the search.
+ if (rowCount == 0)
+ statusMsg = gSearchBundle.getString("searchFailureMessage");
+ else if (rowCount == 1)
+ statusMsg = gSearchBundle.getString("searchSuccessMessage");
+ else
+ statusMsg = gSearchBundle.getFormattedString("searchSuccessMessages",
+ [rowCount]);
+
+ gStatusFeedback.showStatusString(statusMsg);
+ },
+};
+
function searchOnLoad()
{
initializeSearchWidgets();
initializeSearchWindowWidgets();
messenger = Components.classes["@mozilla.org/messenger;1"]
.createInstance(Components.interfaces.nsIMessenger);
gSearchBundle = document.getElementById("bundle_search");
gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForSearchButton"));
gSearchStopButton.setAttribute("accesskey", gSearchBundle.getString("labelForSearchButton.accesskey"));
gMessengerBundle = document.getElementById("bundle_messenger");
- setupDatasource();
- setupSearchListener();
+
+ gMessageDisplay = new NeverVisisbleMessageDisplayWidget();
+ gFolderDisplay = new SearchFolderDisplayWidget(gMessageDisplay);
+ gFolderDisplay.messenger = messenger;
+ gFolderDisplay.msgWindow = msgWindow;
+ gFolderDisplay.tree = document.getElementById("threadTree");
+ gFolderDisplay.treeBox = gFolderDisplay.tree.boxObject.QueryInterface(
+ Components.interfaces.nsITreeBoxObject);
+ gFolderDisplay.view.openSearchView();
+ gFolderDisplay.makeActive();
+
+ gFolderDisplay.setVisibleColumns({subjectCol: true,
+ senderCol: true,
+ dateCol: true,
+ locationCol: true,
+ });
if (window.arguments && window.arguments[0])
selectFolder(window.arguments[0].folder);
+ // trigger searchTermOverlay.js to create the first criterion
onMore(null);
+ // make sure all the buttons are configured
UpdateMailSearch("onload");
-
- // hide and remove these columns from the column picker. you can't thread search results
- HideSearchColumn("threadCol"); // since you can't thread search results
- HideSearchColumn("totalCol"); // since you can't thread search results
- HideSearchColumn("unreadCol"); // since you can't thread search results
- HideSearchColumn("unreadButtonColHeader");
- HideSearchColumn("statusCol");
- HideSearchColumn("flaggedCol");
- HideSearchColumn("idCol");
- HideSearchColumn("junkStatusCol");
- HideSearchColumn("accountCol");
-
- // we want to show the location column for search
- ShowSearchColumn("locationCol");
}
function searchOnUnload()
{
- // unregister listeners
- gSearchSession.unregisterListener(gViewSearchListener);
- gSearchSession.unregisterListener(gSearchNotificationListener);
+ gFolderDisplay.close();
+ top.controllers.removeController(nsSearchResultsController);
- Components.classes["@mozilla.org/messenger/services/session;1"]
- .getService(Components.interfaces.nsIMsgMailSession)
- .RemoveFolderListener(gFolderListener);
-
- if (gSearchView) {
- gSearchView.close();
- gSearchView = null;
- }
-
- top.controllers.removeController(nsSearchResultsController);
-
- // release this early because msgWindow holds a weak reference
- msgWindow.rootDocShell = null;
+ // release this early because msgWindow holds a weak reference
+ msgWindow.rootDocShell = null;
}
function initializeSearchWindowWidgets()
{
gFolderPicker = document.getElementById("searchableFolders");
gSearchStopButton = document.getElementById("search-button");
hideMatchAllItem();
-
+
msgWindow = Components.classes["@mozilla.org/messenger/msgwindow;1"]
.createInstance(nsIMsgWindow);
msgWindow.domWindow = window;
msgWindow.rootDocShell.appType = Components.interfaces.nsIDocShell.APP_TYPE_MAIL;
gStatusFeedback = new nsMsgStatusFeedback();
msgWindow.statusFeedback = gStatusFeedback;
// functionality to enable/disable buttons using nsSearchResultsController
// depending of whether items are selected in the search results thread pane.
top.controllers.insertControllerAt(0, nsSearchResultsController);
}
function onSearchStop() {
- gSearchSession.interruptSearch();
+ gFolderDisplay.view.search.session.interruptSearch();
}
function onResetSearch(event) {
- onReset(event);
-
- var tree = GetThreadTree();
- tree.treeBoxObject.view = null;
- gStatusFeedback.showStatusString("");
+ onReset(event);
+ gFolderDisplay.view.search.clear();
+
+ gStatusFeedback.showStatusString("");
}
-function selectFolder(folder)
+function selectFolder(folder)
{
var folderURI;
// if we can't search messages on this folder, just select the first one
if (!folder || !folder.server.canSearchMessages ||
(folder.flags & Components.interfaces.nsMsgFolderFlags.Virtual)) {
// find first item in our folder picker menu list
folderURI = gFolderPicker.firstChild.tree.builderView.getResourceAtIndex(0).Value;
} else {
folderURI = folder.URI;
}
updateSearchFolderPicker(folderURI);
}
-function updateSearchFolderPicker(folderURI)
-{
+function updateSearchFolderPicker(folderURI)
+{
SetFolderPicker(folderURI, gFolderPicker.id);
// use the URI to get the real folder
gCurrentFolder = GetMsgFolderFromUri(folderURI);
var searchLocalSystem = document.getElementById("checkSearchLocalSystem");
if (searchLocalSystem)
searchLocalSystem.disabled = gCurrentFolder.server.searchScope == nsMsgSearchScope.offlineMail;
@@ -384,80 +370,96 @@ function onChooseFolder(event) {
}
}
function onEnterInSearchTerm()
{
// on enter
// if not searching, start the search
// if searching, stop and then start again
- if (gSearchStopButton.getAttribute("label") == gSearchBundle.getString("labelForSearchButton")) {
- onSearch();
+ if (gSearchStopButton.getAttribute("label") == gSearchBundle.getString("labelForSearchButton")) {
+ onSearch();
}
else {
onSearchStop();
onSearch();
}
}
function onSearch()
{
- // set the view. do this on every search, to
- // allow the tree to reset itself
- var treeView = gSearchView.QueryInterface(Components.interfaces.nsITreeView);
- if (treeView)
- {
- var tree = GetThreadTree();
- tree.treeBoxObject.view = treeView;
- }
-
- gSearchSession.clearScopes();
- // tell the search session what the new scope is
- if (!gCurrentFolder.isServer && !gCurrentFolder.noSelect)
- gSearchSession.addScopeTerm(GetScopeForFolder(gCurrentFolder),
- gCurrentFolder);
+ let viewWrapper = gFolderDisplay.view;
+ let searchTerms = getSearchTerms();
- var searchSubfolders = document.getElementById("checkSearchSubFolders").checked;
- if (gCurrentFolder && (searchSubfolders || gCurrentFolder.isServer || gCurrentFolder.noSelect))
- {
- AddSubFolders(gCurrentFolder);
- }
- // reflect the search widgets back into the search session
- saveSearchTerms(gSearchSession.searchTerms, gSearchSession);
-
- try
- {
- gSearchSession.search(msgWindow);
- }
- catch(ex)
- {
- dump("Search Exception\n");
- }
- // refresh the tree after the search starts, because initiating the
- // search will cause the datasource to clear itself
+ viewWrapper.beginViewUpdate();
+ viewWrapper.search.userTerms = searchTerms.length ? searchTerms : null;
+ viewWrapper.searchFolders = getSearchFolders();
+ viewWrapper.endViewUpdate();
}
-function AddSubFolders(folder) {
+/**
+ * Get the current set of search terms, returning them as a list. We filter out
+ * dangerous and insane predicates.
+ */
+function getSearchTerms() {
+ let termCreator = gFolderDisplay.view.search.session;
+
+ let searchTerms = [];
+ // searchTermOverlay stores wrapper objects in its gSearchTerms array. Pluck
+ // them.
+ for (let iTerm = 0; iTerm < gSearchTerms.length; iTerm++) {
+ let termWrapper = gSearchTerms[iTerm].obj;
+ let realTerm = termCreator.createTerm();
+ termWrapper.saveTo(realTerm);
+ // A header search of "" is illegal for IMAP and will cause us to
+ // explode. You don't want that and I don't want that. So let's check
+ // if the bloody term is a subject search on a blank string, and if it
+ // is, let's secretly not add the term. Everyone wins!
+ if ((realTerm.attrib != Components.interfaces.nsMsgSearchAttrib.Subject) ||
+ (realTerm.value.str != ""))
+ searchTerms.push(realTerm);
+ }
+
+ return searchTerms;
+}
+
+/**
+ * @return the list of folders the search should cover.
+ */
+function getSearchFolders() {
+ let searchFolders = [];
+
+ if (!gCurrentFolder.isServer && !gCurrentFolder.noSelect)
+ searchFolders.push(gCurrentFolder);
+
+ var searchSubfolders =
+ document.getElementById("checkSearchSubFolders").checked;
+ if (gCurrentFolder &&
+ (searchSubfolders || gCurrentFolder.isServer || gCurrentFolder.noSelect))
+ AddSubFolders(gCurrentFolder, searchFolders);
+
+ return searchFolders;
+}
+
+function AddSubFolders(folder, outFolders) {
var subFolders = folder.subFolders;
- while (subFolders.hasMoreElements())
- {
+ while (subFolders.hasMoreElements()) {
var nextFolder =
subFolders.getNext().QueryInterface(Components.interfaces.nsIMsgFolder);
- if (!(nextFolder.flags & Components.interfaces.nsMsgFolderFlags.Virtual))
- {
+ if (!(nextFolder.flags & Components.interfaces.nsMsgFolderFlags.Virtual)) {
if (!nextFolder.noSelect)
- gSearchSession.addScopeTerm(GetScopeForFolder(nextFolder), nextFolder);
+ outFolders.push(nextFolder);
- AddSubFolders(nextFolder);
+ AddSubFolders(nextFolder, outFolders);
}
}
}
-function AddSubFoldersToURI(folder)
+function AddSubFoldersToURI(folder)
{
var returnString = "";
var subFolders = folder.subFolders;
while (subFolders.hasMoreElements())
{
var nextFolder =
@@ -479,17 +481,17 @@ function AddSubFoldersToURI(folder)
returnString += subFoldersString;
}
}
}
return returnString;
}
-function GetScopeForFolder(folder)
+function GetScopeForFolder(folder)
{
var searchLocalSystem = document.getElementById("checkSearchLocalSystem");
return searchLocalSystem && searchLocalSystem.checked ? nsMsgSearchScope.offlineMail : folder.server.searchScope;
}
var nsMsgViewSortType = Components.interfaces.nsMsgViewSortType;
var nsMsgViewSortOrder = Components.interfaces.nsMsgViewSortOrder;
var nsMsgViewFlagsType = Components.interfaces.nsMsgViewFlagsType;
@@ -502,287 +504,89 @@ function goUpdateSearchItems(commandset)
var commandID = commandset.childNodes[i].getAttribute("id");
if (commandID)
{
goUpdateCommand(commandID);
}
}
}
-function nsMsgSearchCommandUpdater()
-{}
-
-nsMsgSearchCommandUpdater.prototype =
-{
- updateCommandStatus : function()
- {
- // the back end is smart and is only telling us to update command status
- // when the # of items in the selection has actually changed.
- document.commandDispatcher.updateCommands('mail-search');
- },
- displayMessageChanged : function(aFolder, aSubject, aKeywords)
- {
- },
-
- updateNextMessageAfterDelete : function()
- {
- SetNextMessageAfterDelete();
- },
-
- summarizeSelection : function() {return false},
-
- QueryInterface : function(iid)
- {
- if (iid.equals(Components.interfaces.nsIMsgDBViewCommandUpdater) ||
- iid.equals(Components.interfaces.nsISupports))
- return this;
-
- throw Components.results.NS_NOINTERFACE;
- }
-}
-
-function setupDatasource() {
- gSearchView = Components.classes["@mozilla.org/messenger/msgdbview;1?type=search"].createInstance(Components.interfaces.nsIMsgDBView);
- var count = new Object;
- var cmdupdator = new nsMsgSearchCommandUpdater();
-
- gSearchView.init(messenger, msgWindow, cmdupdator);
- gSearchView.open(null, nsMsgViewSortType.byId, nsMsgViewSortOrder.ascending, nsMsgViewFlagsType.kNone, count);
-
- // the thread pane needs to use the search datasource (to get the
- // actual list of messages) and the message datasource (to get any
- // attributes about each message)
- gSearchSession = Components.classes[searchSessionContractID].createInstance(Components.interfaces.nsIMsgSearchSession);
-
- var nsIFolderListener = Components.interfaces.nsIFolderListener;
- var notifyFlags = nsIFolderListener.event;
- Components.classes["@mozilla.org/messenger/services/session;1"]
- .getService(Components.interfaces.nsIMsgMailSession)
- .AddFolderListener(gFolderListener, notifyFlags);
-
- // the datasource is a listener on the search results
- gViewSearchListener = gSearchView.QueryInterface(Components.interfaces.nsIMsgSearchNotify);
- gSearchSession.registerListener(gViewSearchListener);
-}
-
-
-function setupSearchListener()
-{
- // Setup the javascript object as a listener on the search results
- gSearchSession.registerListener(gSearchNotificationListener);
-}
-
-// stuff after this is implemented to make the thread pane work
-function GetFolderDatasource()
-{
- if (!gFolderDatasource)
- gFolderDatasource = Components.classes["@mozilla.org/rdf/datasource;1?name=mailnewsfolders"]
- .getService(Components.interfaces.nsIRDFDataSource);
- return gFolderDatasource;
-}
-
-// used to determine if we should try to load a message
-function IsThreadAndMessagePaneSplitterCollapsed()
-{
- return true;
-}
-
// used to toggle functionality for Search/Stop button.
function onSearchButton(event)
{
if (event.target.label == gSearchBundle.getString("labelForSearchButton"))
onSearch();
else
onSearchStop();
}
// threadPane.js will be needing this, too
function GetNumSelectedMessages()
{
- try {
- return gSearchView.numSelected;
- }
- catch (ex) {
- return 0;
- }
-}
-
-function GetDBView()
-{
- return gSearchView;
+ return gFolderDisplay.treeSelection.count;
}
function MsgDeleteSelectedMessages(aCommandType)
{
// we don't delete news messages, we just return in that case
- if (isNewsURI(gSearchView.getURIForViewIndex(0)))
+ if (gFolderDisplay.selectedMessageIsNews)
return;
// if mail messages delete
- SetNextMessageAfterDelete();
- gSearchView.doCommand(aCommandType);
-}
-
-function SetNextMessageAfterDelete()
-{
- gNextMessageViewIndexAfterDelete = gSearchView.msgToSelectAfterDelete;
-}
-
-function HandleDeleteOrMoveMessageFailed(folder)
-{
- gNextMessageViewIndexAfterDelete = -2;
-}
-
-function HandleDeleteOrMoveMessageCompleted(folder)
-{
- var treeView = gSearchView.QueryInterface(Components.interfaces.nsITreeView);
- var treeSelection = treeView.selection;
- var viewSize = treeView.rowCount;
-
- if (gNextMessageViewIndexAfterDelete == -2) {
- // a move or delete can cause our selection can change underneath us.
- // this can happen when the user
- // deletes message from the stand alone msg window
- // or the three pane
- if (!treeSelection) {
- // this can happen if you open the search window
- // and before you do any searches
- // and you do delete from another mail window
- return;
- }
- else if (treeSelection.count == 0) {
- // this can happen if you double clicked a message
- // in the thread pane, and deleted it from the stand alone msg window
- // see bug #185147
- treeSelection.clearSelection();
-
- UpdateMailSearch("delete from another view, 0 rows now selected");
- }
- else if (treeSelection.count == 1) {
- // this can happen if you had two messages selected
- // in the search results pane, and you deleted one of them from another view
- // (like the view in the stand alone msg window or the three pane)
- // since one item is selected, we should load it.
- var startIndex = {};
- var endIndex = {};
- treeSelection.getRangeAt(0, startIndex, endIndex);
-
- // select the selected item, so we'll load it
- treeSelection.select(startIndex.value);
- treeView.selectionChanged();
-
- EnsureRowInThreadTreeIsVisible(startIndex.value);
- UpdateMailSearch("delete from another view, 1 row now selected");
- }
- else {
- // this can happen if you have more than 2 messages selected
- // in the search results pane, and you deleted one of them from another view
- // (like the view in the stand alone msg window or the three pane)
- // since multiple messages are still selected, do nothing.
- }
- }
- else {
- if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None && gNextMessageViewIndexAfterDelete >= viewSize)
- {
- if (viewSize > 0)
- gNextMessageViewIndexAfterDelete = viewSize - 1;
- else
- {
- gNextMessageViewIndexAfterDelete = nsMsgViewIndex_None;
-
- // there is nothing to select since viewSize is 0
- treeSelection.clearSelection();
-
- UpdateMailSearch("delete from current view, 0 rows left");
- }
- }
-
- // if we are about to set the selection with a new element then DON'T clear
- // the selection then add the next message to select. This just generates
- // an extra round of command updating notifications that we are trying to
- // optimize away.
- if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None)
- {
- treeSelection.select(gNextMessageViewIndexAfterDelete);
- // since gNextMessageViewIndexAfterDelete probably has the same value
- // as the last index we had selected, the tree isn't generating a new
- // selectionChanged notification for the tree view. So we aren't loading the
- // next message. to fix this, force the selection changed update.
- if (treeView)
- treeView.selectionChanged();
-
- EnsureRowInThreadTreeIsVisible(gNextMessageViewIndexAfterDelete);
-
- // XXX TODO
- // I think there is a bug in the suppression code above.
- // what if I have two rows selected, and I hit delete,
- // and so we load the next row.
- // what if I have commands that only enable where
- // exactly one row is selected?
- UpdateMailSearch("delete from current view, at least one row selected");
- }
- }
-
- // default value after delete/move/copy is over
- gNextMessageViewIndexAfterDelete = -2;
-
- // something might have been deleted, so update the status text
- SetAdvancedSearchStatusText(viewSize);
+ gFolderDisplay.hintAboutToDeleteMessages();
+ gFolderDisplay.doCommand(aCommandType);
}
function MoveMessageInSearch(destFolder)
{
- try {
- // get the msg folder we're moving messages into
- // if the id (uri) is not set, use file-uri which is set for
- // "File Here"
- var destUri = destFolder.getAttribute('id');
- if (destUri.length == 0) {
- destUri = destFolder.getAttribute('file-uri')
- }
+ // Get the msg folder we're moving messages into.
+ // If the id (uri) is not set, use file-uri which is set for
+ // "File Here".
+ let destUri = destFolder.getAttribute('id');
+ if (destUri.length == 0)
+ destUri = destFolder.getAttribute('file-uri');
- var destMsgFolder = GetMsgFolderFromUri(destUri).QueryInterface(Components.interfaces.nsIMsgFolder);
+ let destMsgFolder = GetMsgFolderFromUri(destUri).QueryInterface(
+ Components.interfaces.nsIMsgFolder);
- // we don't move news messages, we copy them
- if (isNewsURI(gSearchView.getURIForViewIndex(0))) {
- gSearchView.doCommandWithFolder(nsMsgViewCommandType.copyMessages, destMsgFolder);
- }
- else {
- SetNextMessageAfterDelete();
- gSearchView.doCommandWithFolder(nsMsgViewCommandType.moveMessages, destMsgFolder);
- }
- }
- catch (ex) {
- dump("MsgMoveMessage failed: " + ex + "\n");
- }
+ // we don't move news messages, we copy them
+ if (gFolderDisplay.selectedMessageIsNews) {
+ gFolderDisplay.doCommandWithFolder(nsMsgViewCommandType.copyMessages,
+ destMsgFolder);
+ }
+ else {
+ gFolderDisplay.hintAboutToDeleteMessages();
+ gFolderDisplay.doCommandWithFolder(nsMsgViewCommandType.moveMessages,
+ destMsgFolder);
+ }
}
function GoToFolder()
{
- var hdr = gSearchView.hdrForFirstSelectedMessage;
- MsgOpenNewWindowForFolder(hdr.folder.URI, hdr.messageKey);
+ MsgOpenNewWindowForFolder(gFolderDisplay.selectedMessage);
}
function BeginDragThreadPane(event)
{
// no search pane dnd yet
return false;
}
function saveAsVirtualFolder()
{
- searchFolderURIs = window.arguments[0].folder.URI;
+ var searchFolderURIs = window.arguments[0].folder.URI;
var searchSubfolders = document.getElementById("checkSearchSubFolders").checked;
if (gCurrentFolder && (searchSubfolders || gCurrentFolder.isServer || gCurrentFolder.noSelect))
{
var subFolderURIs = AddSubFoldersToURI(gCurrentFolder);
if (subFolderURIs.length > 0)
searchFolderURIs += '|' + subFolderURIs;
}
var dialog = window.openDialog("chrome://messenger/content/virtualFolderProperties.xul", "",
"chrome,titlebar,modal,centerscreen",
- {folder:window.arguments[0].folder,
- searchTerms:gSearchSession.searchTerms,
+ {folder: window.arguments[0].folder,
+ searchTerms: toXPCOMArray(getSearchTerms(),
+ Components.interfaces.nsISupportsArray),
searchFolderURIs: searchFolderURIs});
}
--- a/mail/base/content/SearchDialog.xul
+++ b/mail/base/content/SearchDialog.xul
@@ -53,16 +53,18 @@
style="width: 52em; height: 34em;"
persist="screenX screenY width height sizemode">
<stringbundle id="bundle_search" src="chrome://messenger/locale/search.properties"/>
<stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
<stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
<script type="application/x-javascript" src="chrome://messenger/content/mailWindow.js"/>
+ <script type="application/x-javascript" src="chrome://messenger/content/folderDisplay.js"/>
+ <script type="application/x-javascript" src="chrome://messenger/content/messageDisplay.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/threadPane.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/msgMail3PaneWindow.js"/>
<script type="application/x-javascript" src="chrome://global/content/globalOverlay.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/mailCommands.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/mailWindowOverlay.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/commandglue.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/SearchDialog.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/messengerdnd.js"/>
@@ -110,34 +112,35 @@
<label value="&searchHeading.label;" accesskey="&searchHeading.accesskey;"
control="searchableFolders"/>
<menulist id="searchableFolders" flex="2">
<menupopup class="searchPopup" oncommand="updateSearchFolderPicker(this.getAttribute('uri'));"/>
</menulist>
<spacer flex="10"/>
<button id="search-button" oncommand="onSearchButton(event);" default="true"/>
</hbox>
-
+
<hbox align="center">
<checkbox id="checkSearchSubFolders" label="&searchSubfolders.label;" checked="true" accesskey="&searchSubfolders.accesskey;"/>
<spacer flex="10"/>
<button label="&resetButton.label;" oncommand="onResetSearch(event);" accesskey="&resetButton.accesskey;"/>
</hbox>
</vbox>
<hbox flex="1">
<vbox id="searchTermListBox" flex="1"/>
</hbox>
</vbox>
-
+
<splitter id="gray_horizontal_splitter" collapse="after" persist="state"/>
-
+
<vbox id="searchResults" flex="4" persist="height">
<vbox id="searchResultListBox" flex="1">
- <tree id="threadTree" persist="lastfoldersent" flex="1" enableColumnDrag="true" _selectDelay="500" class="plain focusring" lastfoldersent="false"
+ <tree id="threadTree" class="plain" persist="lastfoldersent" flex="1"
+ enableColumnDrag="true" _selectDelay="500" lastfoldersent="false"
disableKeyNavigation="true"
context="mailContext"
onkeypress="ThreadPaneKeyPress(event);"
onselect="ThreadPaneSelectionChanged();">
<treecols id="threadCols" pickertooltiptext="&columnChooser.tooltip;">
<treecol id="threadCol" persist="hidden ordinal" fixed="true" cycler="true"
class="treecol-image threadColumnHeader" currentView="unthreaded"
label="&threadColumn.label;" tooltiptext="&threadColumn.tooltip;"/>
--- a/mail/base/content/aboutRights.xhtml
+++ b/mail/base/content/aboutRights.xhtml
@@ -43,17 +43,17 @@
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
- <title>&rights.pagetitle;</title>
+ <title>&rights.title;</title>
<link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
</head>
<body id="your-rights" dir="&rights.locale-direction;" class="aboutPageWideContainer">
<h1>&rights.intro-header;</h1>
<p>&rights.intro;</p>
--- a/mail/base/content/commandglue.js
+++ b/mail/base/content/commandglue.js
@@ -1,485 +1,111 @@
-# -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is Mozilla Communicator client code, released
-# March 31, 1998.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 1998-1999
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# Jan Varga <varga@nixcorp.com>
-# HÃ¥kan Waara (hwaara@chello.se)
-# David Bienvenu (bienvenu@nventure.com)
-# Jeremy Morton (bugzilla@game-point.net)
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Jan Varga <varga@nixcorp.com>
+ * HÃ¥kan Waara (hwaara@chello.se)
+ * David Bienvenu (bienvenu@nventure.com)
+ * Jeremy Morton (bugzilla@game-point.net)
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
/*
* Command-specific code. This stuff should be called by the widgets
*/
Components.utils.import("resource://gre/modules/iteratorUtils.jsm");
//NOTE: gMessengerBundle and gBrandBundle must be defined and set
// for this Overlay to work properly
-var gFolderJustSwitched = false;
-var gVirtualFolderTerms;
-var gXFVirtualFolderTerms;
-var gCurrentVirtualFolderUri;
-var gPrevFolderFlags;
-var gPrevSelectedFolder;
var gMsgFolderSelected;
-function setTitleFromFolder(msgfolder, subject)
-{
- var wintype = document.documentElement.getAttribute('windowtype');
- var title;
-
- // If we are showing the mail:3pane. Never include the subject of the selected
- // message in the title. ("Inbox - My Mail - Mozilla Thunderbird")
- // If we are a stand alone message window, we should show the Subject
- // and the product but not the account name: "Re: New window Title - Mozilla Thunderbird"
-
- if (wintype == "mail:messageWindow")
- title = subject ? subject : "";
- else if (msgfolder)
- {
- title = msgfolder.prettyName;
-
- if (!msgfolder.isServer)
- {
- var server = msgfolder.server;
- var middle;
- var end;
- if (server.type == "nntp") {
- // <folder> on <hostname>
- middle = gMessengerBundle.getString("titleNewsPreHost");
- end = server.hostName;
- }
- else {
- // <folder> - <server.prettyName>
- middle = "-";
- end = server.prettyName;
- }
- if (middle) title += " " + middle;
- if (end) title += " " + end;
- }
- }
-
-#ifndef XP_MACOSX
- title += " - " + gBrandBundle.getString("brandFullName");
-#endif
- document.title = title;
-}
-
function UpdateMailToolbar(caller)
{
//dump("XXX update mail-toolbar " + caller + "\n");
document.commandDispatcher.updateCommands('mail-toolbar');
// hook for extra toolbar items
var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
observerService.notifyObservers(window, "mail:updateToolbarItems", null);
}
-/**
- * @param folder - If viewFolder is a single folder saved
- - search, this folder is the scope of the
- - saved search, the real, underlying folder.
- - Otherwise, it's the same as the viewFolder.
- * @param viewFolder - nsIMsgFolder selected in the folder pane.
- - Will be the same as folder, except if
- - it's a single folder saved search.
- * @param viewType - nsMsgViewType (see nsIMsgDBView.idl)
- * @param viewFlags - nsMsgViewFlagsType (see nsIMsgDBView.idl)
- * @param sortType - nsMsgViewSortType (see nsIMsgDBView.idl)
- * @param sortOrder - nsMsgViewSortOrder (see nsIMsgDBView.idl)
- **/
-function ChangeFolder(folder, viewFolder, viewType, viewFlags, sortType, sortOrder)
-{
- if (folder.URI == gCurrentLoadingFolderURI)
- return;
-
- // hook for extra toolbar items
- var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
- observerService.notifyObservers(window, "mail:setupToolbarItems", folder.URI);
-
- try {
- setTitleFromFolder(viewFolder, null);
- } catch (ex) {
- dump("error setting title: " + ex + "\n");
- }
-
- //if it's a server, clear the threadpane and don't bother trying to load.
- if(folder.isServer)
- {
- msgWindow.openFolder = null;
- ClearThreadPane();
- UpdateStatusQuota(null);
- // Load AccountCentral page here.
- ShowAccountCentral();
- return;
- }
- else
- {
- if (folder.server.displayStartupPage)
- {
- gDisplayStartupPage = true;
- folder.server.displayStartupPage = false;
- }
- }
-
- // If the user clicks on msgfolder, time to display thread pane and message pane.
- ShowThreadPane();
-
- gCurrentLoadingFolderURI = folder.URI;
- gNextMessageAfterDelete = null; // forget what message to select, if any
-
- gCurrentFolderToReroot = folder.URI;
- gCurrentLoadingFolderViewFlags = viewFlags;
- gCurrentLoadingFolderViewType = viewType;
- gCurrentLoadingFolderSortType = sortType;
- gCurrentLoadingFolderSortOrder = sortOrder;
-
- var showMessagesAfterLoading;
- try {
- var server = folder.server;
- if (gPrefBranch.getBoolPref("mail.password_protect_local_cache"))
- {
- showMessagesAfterLoading = server.passwordPromptRequired;
- // servers w/o passwords (like local mail) will always be non-authenticated.
- // So we need to use the account manager for that case.
- }
- else
- showMessagesAfterLoading = false;
- }
- catch (ex) {
- showMessagesAfterLoading = false;
- }
-
- if (viewType != nsMsgViewType.eShowVirtualFolderResults && (folder.manyHeadersToDownload || showMessagesAfterLoading))
- {
- gRerootOnFolderLoad = true;
- try
- {
- ClearThreadPane();
- SetBusyCursor(window, true);
- folder.startFolderLoading();
- folder.updateFolder(msgWindow);
- }
- catch(ex)
- {
- SetBusyCursor(window, false);
- dump("Error loading with many headers to download: " + ex + "\n");
- }
- }
- else
- {
- if (viewType != nsMsgViewType.eShowVirtualFolderResults)
- SetBusyCursor(window, true);
- RerootFolder(folder.URI, folder, viewType, viewFlags, sortType, sortOrder);
- gRerootOnFolderLoad = false;
- folder.startFolderLoading();
-
- //Need to do this after rerooting folder. Otherwise possibility of receiving folder loaded
- //notification before folder has actually changed.
- if (viewType != nsMsgViewType.eShowVirtualFolderResults)
- folder.updateFolder(msgWindow);
- }
-}
-
function isNewsURI(uri)
{
if (!uri || uri[0] != 'n') {
return false;
}
else {
return ((uri.substring(0,6) == "news:/") || (uri.substring(0,14) == "news-message:/"));
}
}
-function UpdateColumnsForView(folder, viewType)
-{
- const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
- // if this is the drafts, sent, or send later folder,
- // we show "Recipient" instead of "Author"
- SetSentFolderColumns(IsSpecialFolder(folder, nsMsgFolderFlags.SentMail | nsMsgFolderFlags.Drafts | nsMsgFolderFlags.Queue, true));
- ShowLocationColumn(viewType == nsMsgViewType.eShowVirtualFolderResults);
- // Only show 'Received' column for e-mails. For newsgroup messages, the 'Date' header is as reliable as an e-mail's
- // 'Received' header, as it is replaced with the news server's (more reliable) date.
- UpdateReceivedColumn(folder);
-}
-
-function RerootFolder(uri, newFolder, viewType, viewFlags, sortType, sortOrder)
-{
- viewDebug("In reroot folder, sortType = " + sortType + "viewType = " + viewType + "\n");
-
- if (sortType == 0)
- {
- try
- {
- var dbFolderInfo = newFolder.msgDatabase.dBFolderInfo;
- sortType = dbFolderInfo.sortType;
- sortOrder = dbFolderInfo.sortOrder;
- viewFlags = dbFolderInfo.viewFlags;
- viewType = dbFolderInfo.viewType;
- dbFolderInfo = null;
- }
- catch(ex)
- {
- dump("invalid db in RerootFolder: " + ex + "\n");
- }
- }
-
- // workaround for #39655
- gFolderJustSwitched = true;
-
- ClearThreadPaneSelection();
-
- //Clear the new messages of the old folder
- var oldFolder = gPrevSelectedFolder;
- if (oldFolder) {
- oldFolder.clearNewMessages();
- oldFolder.hasNewMessages = false;
- }
-
- //Set the window's new open folder.
- msgWindow.openFolder = newFolder;
-
- //the new folder being selected should have its biff state get cleared.
- if(newFolder)
- {
- newFolder.biffState =
- Components.interfaces.nsIMsgFolder.nsMsgBiffState_NoMail;
- }
-
- //Clear out the thread pane so that we can sort it with the new sort id without taking any time.
- // folder.setAttribute('ref', "");
-
- // null this out, so we don't try sort.
- if (gDBView) {
- gDBView.close();
- gDBView = null;
- }
-
- // cancel the pending mark as read timer
- ClearPendingReadTimer();
-
- UpdateColumnsForView(newFolder, viewType);
- // now create the db view, which will sort it.
- CreateDBView(newFolder, viewType, viewFlags, sortType, sortOrder);
-
- if (oldFolder)
- {
- /* we don't null out the db reference for inbox because inbox is like the "main" folder
- and performance outweighs footprint*/
- if (!IsSpecialFolder(oldFolder, Components.interfaces.nsMsgFolderFlags.Inbox, false))
- {
- if (oldFolder.URI != newFolder.URI)
- oldFolder.msgDatabase = null;
- }
- }
- // that should have initialized gDBView, now re-root the thread pane
- RerootThreadPane();
- SetUpToolbarButtons(uri);
- UpdateStatusMessageCounts(gMsgFolderSelected);
-
- // hook for extra toolbar items
- var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
- observerService.notifyObservers(window, "mail:updateToolbarItems", null);
- // this is to kick off cross-folder searches for virtual folders.
- if (gSearchSession && !gVirtualFolderTerms) // another var might be better...
- {
- viewDebug("doing a xf folder search in rerootFolder\n");
- gCurrentLoadingFolderURI = ""
- ViewChangeByFolder(newFolder);
- gPreQuickSearchView = null; // don't remember the cross folder search
- ScrollToMessageAfterFolderLoad(newFolder);
- }
-}
-
function SwitchView(command)
{
// when switching thread views, we might be coming out of quick search
// or a message view.
// first set view picker to all
- if (gCurrentViewValue != kViewItemAll)
- ViewChangeByValue(kViewItemAll);
+ if (gFolderDisplay.view.mailViewIndex != kViewItemAll)
+ gFolderDisplay.view.setMailView(kViewItemAll);
- // clear the QS text, if we need to
- ClearQSIfNecessary();
-
- var oldSortType, oldSortOrder, viewFlags, viewType, db;
- // now switch views
- if (gDBView)
- {
- oldSortType = gDBView.sortType;
- oldSortOrder = gDBView.sortOrder;
- viewFlags = gDBView.viewFlags;
- viewType = gDBView.viewType;
- db = gDBView.db;
- gDBView.close();
- gDBView = null;
- }
- else
- {
- oldSortType = nsMsgViewSortType.byThread;
- oldSortOrder = nsMsgViewSortOrder.ascending;
- viewFlags = gCurViewFlags;
- viewType = nsMsgViewType.eShowAllThreads;
- db = null;
- }
switch(command)
{
// "All" threads and "Unread" threads don't change threading state
case "cmd_viewAllMsgs":
- viewType = nsMsgViewType.eShowAllThreads;
- viewFlags = viewFlags & ~nsMsgViewFlagsType.kUnreadOnly;
+ gFolderDisplay.view.showUnreadOnly = false;
break;
case "cmd_viewUnreadMsgs":
- viewType = nsMsgViewType.eShowAllThreads;
- viewFlags = viewFlags | nsMsgViewFlagsType.kUnreadOnly;
+ gFolderDisplay.view.showUnreadOnly = true;
break;
// "Threads with Unread" and "Watched Threads with Unread" force threading
case "cmd_viewWatchedThreadsWithUnread":
- viewType = nsMsgViewType.eShowWatchedThreadsWithUnread;
- viewFlags |= nsMsgViewFlagsType.kThreadedDisplay;
+ gFolderDisplay.view.specialViewWatchedThreadsWithUnread = true;
break;
case "cmd_viewThreadsWithUnread":
- viewType = nsMsgViewType.eShowThreadsWithUnread;
- viewFlags |= nsMsgViewFlagsType.kThreadedDisplay;
+ gFolderDisplay.view.specialViewThreadsWithUnread = true;
break;
// "Ignored Threads" toggles 'ignored' inclusion --
// but it also resets 'With Unread' views to 'All'
case "cmd_viewIgnoredThreads":
- viewType = nsMsgViewType.eShowAllThreads;
- if (viewFlags & nsMsgViewFlagsType.kShowIgnored)
- viewFlags = viewFlags & ~nsMsgViewFlagsType.kShowIgnored;
- else
- viewFlags = viewFlags | nsMsgViewFlagsType.kShowIgnored;
+ gFolderDisplay.view.showIgnored = !gFolderDisplay.view.showIgnored;
break;
}
-
- if (db && viewType == nsMsgViewType.eShowVirtualFolderResults)
- {
- db.dBFolderInfo.viewFlags = viewFlags;
- gMsgFolderSelected = null;
- msgWindow.openFolder = null;
- FolderPaneSelectionChange();
- LoadCurrentlyDisplayedMessage();
- }
- else
- {
- CreateDBView(msgWindow.openFolder, viewType, viewFlags, oldSortType,
- oldSortOrder);
- RerootThreadPane();
- }
-}
-
-function SetSentFolderColumns(isSentFolder)
-{
- var tree = GetThreadTree();
-
- var lastFolderSent = tree.getAttribute("lastfoldersent") == "true";
- if (isSentFolder != lastFolderSent)
- {
- var senderColumn = document.getElementById("senderCol");
- var recipientColumn = document.getElementById("recipientCol");
-
- var saveHidden = senderColumn.getAttribute("hidden");
- senderColumn.setAttribute("hidden", senderColumn.getAttribute("swappedhidden"));
- senderColumn.setAttribute("swappedhidden", saveHidden);
-
- saveHidden = recipientColumn.getAttribute("hidden");
- recipientColumn.setAttribute("hidden", recipientColumn.getAttribute("swappedhidden"));
- recipientColumn.setAttribute("swappedhidden", saveHidden);
- }
-
- if(isSentFolder)
- tree.setAttribute("lastfoldersent", "true");
- else
- tree.setAttribute("lastfoldersent", "false");
-}
-
-function ShowLocationColumn(show)
-{
- var col = document.getElementById("locationCol");
- if (col) {
- if (show) {
- col.removeAttribute("hidden");
- col.removeAttribute("ignoreincolumnpicker");
- }
- else {
- col.setAttribute("hidden","true");
- col.setAttribute("ignoreincolumnpicker","true");
- }
- }
-}
-
-function UpdateReceivedColumn(newFolder)
-{
- // Only show 'Received' column for e-mails. For newsgroup messages, the 'Date' header is as reliable as an e-mail's
- // 'Received' header, as it is replaced with the news server's (more reliable) date.
- var receivedColumn = document.getElementById("receivedCol");
-
- const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
- var newFolderShowsRcvd = (newFolder.flags & nsMsgFolderFlags.Mail) &&
- !(newFolder.flags & (nsMsgFolderFlags.Queue | nsMsgFolderFlags.Drafts |
- nsMsgFolderFlags.Templates |
- nsMsgFolderFlags.SentMail));
-
- var tempHidden = receivedColumn.getAttribute("temphidden") == "true";
- var isHidden = receivedColumn.getAttribute("hidden") == "true";
-
- if (!newFolderShowsRcvd && !isHidden)
- {
- // Record state & hide
- receivedColumn.setAttribute("temphidden", "true");
- receivedColumn.setAttribute("hidden", "true");
- }
- else if (newFolderShowsRcvd && tempHidden && isHidden)
- {
- receivedColumn.setAttribute("hidden", "false");
- }
-
- if (newFolderShowsRcvd)
- {
- receivedColumn.removeAttribute("ignoreincolumnpicker");
- receivedColumn.removeAttribute("temphidden");
- }
- else
- receivedColumn.setAttribute("ignoreincolumnpicker", "true");
}
function SetNewsFolderColumns()
{
var sizeColumn = document.getElementById("sizeCol");
if (gDBView.usingLines) {
sizeColumn.setAttribute("label",gMessengerBundle.getString("linesColumnHeader"));
@@ -494,17 +120,17 @@ function UpdateStatusMessageCounts(folde
var unreadElement = GetUnreadCountElement();
var totalElement = GetTotalCountElement();
if(folder && unreadElement && totalElement)
{
var numSelected = GetNumSelectedMessages();
var numUnread = (numSelected > 1) ?
gMessengerBundle.getFormattedString("selectedMsgStatus",
- [numSelected]) :
+ [numSelected]) :
gMessengerBundle.getFormattedString("unreadMsgStatus",
[ folder.getNumUnread(false)]);
var numTotal =
gMessengerBundle.getFormattedString("totalMsgStatus",
[folder.getTotalMessages(false)]);
unreadElement.setAttribute("label", numUnread);
totalElement.setAttribute("label", numTotal);
@@ -637,19 +263,18 @@ function ConvertSortTypeToColumnID(sortK
case nsMsgViewSortType.byAttachments:
columnID = "attachmentCol";
break;
case nsMsgViewSortType.byCustom:
//TODO: either change try() catch to if (property exists) or restore the getColumnHandler() check
try //getColumnHandler throws an errror when the ID is not handled
{
- columnID = gDBView.db.dBFolderInfo.getProperty('customSortCol');
+ columnID = gDBView.curCustomColumn;
}
-
catch (err) { //error - means no handler
dump("ConvertSortTypeToColumnID: custom sort key but no handler for column '" + columnID + "'\n");
columnID = "dateCol";
}
break;
default:
dump("unsupported sort key: " + sortKey + "\n");
@@ -665,314 +290,86 @@ var nsMsgViewFlagsType = Components.inte
var nsMsgViewCommandType = Components.interfaces.nsMsgViewCommandType;
var nsMsgViewType = Components.interfaces.nsMsgViewType;
var nsMsgNavigationType = Components.interfaces.nsMsgNavigationType;
var gDBView = null;
var gCurViewFlags;
var gCurSortType;
-// CreateDBView is called when we have a thread pane. CreateBareDBView is called when there is no
-// tree associated with the view. CreateDBView will call into CreateBareDBView...
-
-function CreateBareDBView(originalView, msgFolder, viewType, viewFlags, sortType, sortOrder)
-{
- var dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=";
- // hack to turn this into an integer, if it was a string
- // it would be a string if it came from localStore.rdf
- viewType = viewType - 0;
-
- switch (viewType) {
- case nsMsgViewType.eShowQuickSearchResults:
- dbviewContractId += "quicksearch";
- break;
- case nsMsgViewType.eShowThreadsWithUnread:
- dbviewContractId += "threadswithunread";
- break;
- case nsMsgViewType.eShowWatchedThreadsWithUnread:
- dbviewContractId += "watchedthreadswithunread";
- break;
- case nsMsgViewType.eShowVirtualFolderResults:
- dbviewContractId += "xfvf";
- break;
- case nsMsgViewType.eShowSearch:
- dbviewContractId += "search";
- break;
- case nsMsgViewType.eShowAllThreads:
- default:
- if (sortType == nsMsgViewSortType.byThread || sortType == nsMsgViewSortType.byId
- || sortType == nsMsgViewSortType.byNone)
- viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
-
- if (viewFlags & nsMsgViewFlagsType.kGroupBySort)
- dbviewContractId += "group";
- else
- dbviewContractId += "threaded";
- break;
- }
-
- // dump ("contract id = " + dbviewContractId + "original view = " + originalView + "\n");
- if (!originalView)
- gDBView = Components.classes[dbviewContractId].createInstance(Components.interfaces.nsIMsgDBView);
-
- gCurViewFlags = viewFlags;
- var count = new Object;
- if (!gThreadPaneCommandUpdater)
- gThreadPaneCommandUpdater = new nsMsgDBViewCommandUpdater();
-
- gCurSortType = sortType;
-
- if (!originalView) {
- gDBView.init(messenger, msgWindow, gThreadPaneCommandUpdater);
- gDBView.open(msgFolder, gCurSortType, sortOrder, viewFlags, count);
- if (viewType == nsMsgViewType.eShowVirtualFolderResults)
- {
- // the view is a listener on the search results
- gViewSearchListener = gDBView.QueryInterface(Components.interfaces.nsIMsgSearchNotify);
- gSearchSession.registerListener(gViewSearchListener);
- }
- }
- else {
- gDBView = originalView.cloneDBView(messenger, msgWindow, gThreadPaneCommandUpdater);
- }
-}
-
-function CreateDBView(msgFolder, viewType, viewFlags, sortType, sortOrder)
-{
- // call the inner create method
- CreateBareDBView(null, msgFolder, viewType, viewFlags, sortType, sortOrder);
-
- // now do tree specific work
-
- // based on the collapsed state of the thread pane/message pane splitter,
- // suppress message display if appropriate.
- gDBView.suppressMsgDisplay = IsMessagePaneCollapsed();
-
- UpdateSortIndicators(gCurSortType, sortOrder);
- var ObserverService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
- ObserverService.notifyObservers(msgFolder, "MsgCreateDBView", viewType + ":" + viewFlags);
-}
function ChangeMessagePaneVisibility(now_hidden)
{
// we also have to hide the File/Attachments menuitem
var node = document.getElementById("fileAttachmentMenu");
if (node)
node.hidden = now_hidden;
- if (gDBView) {
- // the collapsed state is the state after we released the mouse
- // so we take it as it is
- gDBView.suppressMsgDisplay = now_hidden;
- }
+ gMessageDisplay.visible = !now_hidden;
+
var event = document.createEvent('Events');
if (now_hidden) {
event.initEvent('messagepane-hide', false, true);
}
else {
event.initEvent('messagepane-unhide', false, true);
}
document.getElementById("messengerWindow").dispatchEvent(event);
}
function OnMouseUpThreadAndMessagePaneSplitter()
{
- // the collapsed state is the state after we released the mouse
- // so we take it as it is
+ // The collapsed state is the state after we released the mouse,
+ // so we take it as it is.
ChangeMessagePaneVisibility(IsMessagePaneCollapsed());
}
+/**
+ * Our multiplexed tabbing model ends up sending synthetic folder pane
+ * selection change notifications. We want to ignore these because the
+ * user may explicitly re-select a folder intentionally, and we want to
+ * be able to know that. So we filter out the synthetics here.
+ * The tabbing logic sets this global to help us out.
+ */
+var gIgnoreSyntheticFolderPaneSelectionChange = false;
function FolderPaneSelectionChange()
{
- var folderSelection = gFolderTreeView.selection;
-
- // This prevents a folder from being loaded in the case that the user
- // has right-clicked on a folder different from the one that was
- // originally highlighted. On a right-click, the highlight (selection)
- // of a row will be different from the value of currentIndex, thus if
- // the currentIndex is not selected, it means the user right-clicked
- // and we don't want to load the contents of the folder.
- if (!folderSelection.isSelected(folderSelection.currentIndex))
- return;
-
- gVirtualFolderTerms = null;
- gXFVirtualFolderTerms = null;
-
- var folders = GetSelectedMsgFolders();
- if (folders.length == 1)
- {
- var msgFolder = folders[0];
- var uriToLoad = msgFolder.URI;
+ if (gIgnoreSyntheticFolderPaneSelectionChange) {
+ gIgnoreSyntheticFolderPaneSelectionChange = false;
+ return;
+ }
- if (msgFolder == gMsgFolderSelected)
- return;
- // If msgFolder turns out to be a single folder saved search, a virtual folder,
- // realFolder will get set to the underlying folder the
- // saved search is based on.
- var realFolder = msgFolder;
- gPrevSelectedFolder = gMsgFolderSelected;
- gMsgFolderSelected = msgFolder;
- var folderFlags = msgFolder.flags;
- // If this is same folder, and we're not showing a virtual folder
- // then do nothing.
- const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
- if (msgFolder == msgWindow.openFolder &&
- !(folderFlags & nsMsgFolderFlags.Virtual) &&
- !(gPrevFolderFlags & nsMsgFolderFlags.Virtual))
- {
- return;
- }
- else
- {
- const outFolderFlagMask = nsMsgFolderFlags.SentMail |
- nsMsgFolderFlags.Drafts | nsMsgFolderFlags.Queue |
- nsMsgFolderFlags.Templates;
- if (IsSpecialFolder(gMsgFolderSelected, outFolderFlagMask, true))
- {
- if (!gPrevSelectedFolder ||
- !IsSpecialFolder(gPrevSelectedFolder, outFolderFlagMask, true))
- onSearchFolderTypeChanged(true);
- }
- else
- {
- if (!gPrevSelectedFolder ||
- IsSpecialFolder(gPrevSelectedFolder, outFolderFlagMask, true))
- onSearchFolderTypeChanged(false);
- }
+ let folderSelection = gFolderTreeView.selection;
- OnLeavingFolder(gPrevSelectedFolder); // mark all read in last folder
- var sortType = 0;
- var sortOrder = 0;
- var viewFlags = 0;
- var viewType = 0;
- gDefaultSearchViewTerms = null;
- gVirtualFolderTerms = null;
- gXFVirtualFolderTerms = null;
- gPrevFolderFlags = folderFlags;
- gCurrentVirtualFolderUri = null;
- // don't get the db if this folder is a server
- // we're going to be display account central
- if (!(msgFolder.isServer))
- {
- try
- {
- var msgDatabase = msgFolder.msgDatabase;
- if (msgDatabase)
- {
- var dbFolderInfo = msgDatabase.dBFolderInfo;
- sortType = dbFolderInfo.sortType;
- sortOrder = dbFolderInfo.sortOrder;
- viewFlags = dbFolderInfo.viewFlags;
- if (folderFlags & Components.interfaces.nsMsgFolderFlags.Virtual)
- {
- viewType = nsMsgViewType.eShowQuickSearchResults;
- var searchTermString = dbFolderInfo.getCharProperty("searchStr");
- var searchOnline = dbFolderInfo.getBooleanProperty("searchOnline", false);
- // trick the view code into updating the real folder...
- gCurrentVirtualFolderUri = uriToLoad;
- viewDebug("uriToLoad = " + uriToLoad + "\n");
- var srchFolderUri = dbFolderInfo.getCharProperty("searchFolderUri");
- var srchFolderUriArray = srchFolderUri.split('|');
- // cross folder search
- var filterService = Components.classes["@mozilla.org/messenger/services/filters;1"].getService(Components.interfaces.nsIMsgFilterService);
- var filterList = filterService.getTempFilterList(msgFolder);
- var tempFilter = filterList.createFilter("temp");
- filterList.parseCondition(tempFilter, searchTermString);
- if (srchFolderUriArray.length > 1)
- {
- viewType = nsMsgViewType.eShowVirtualFolderResults;
- gXFVirtualFolderTerms = CreateGroupedSearchTerms(tempFilter.searchTerms);
- setupXFVirtualFolderSearch(srchFolderUriArray, gXFVirtualFolderTerms, searchOnline);
- // need to set things up so that reroot folder issues the search
- }
- else
- {
- gSearchSession = null;
- uriToLoad = srchFolderUri;
- // we need to load the db for the actual folder so that many hdrs to download
- // will return false...
- realFolder = GetMsgFolderFromUri(uriToLoad);
- msgDatabase = realFolder.msgDatabase;
- gVirtualFolderTerms = CreateGroupedSearchTerms(tempFilter.searchTerms);
- }
- }
- else
- {
- gSearchSession = null;
- viewFlags = dbFolderInfo.viewFlags;
- viewType = dbFolderInfo.viewType;
- }
- msgDatabase = null;
- dbFolderInfo = null;
- }
- }
- catch (ex)
- {
- dump("failed to get view & sort values. ex = " + ex +"\n");
- }
- }
- if (gDBView && gDBView.viewType == nsMsgViewType.eShowQuickSearchResults)
- {
- if (gPreQuickSearchView) //close cached view before quick search
- {
- gPreQuickSearchView.close();
- gPreQuickSearchView = null;
- }
- ClearQSIfNecessary();
- }
- ClearMessagePane();
+ // This prevents a folder from being loaded in the case that the user
+ // has right-clicked on a folder different from the one that was
+ // originally highlighted. On a right-click, the highlight (selection)
+ // of a row will be different from the value of currentIndex, thus if
+ // the currentIndex is not selected, it means the user right-clicked
+ // and we don't want to load the contents of the folder.
+ if (!folderSelection.isSelected(folderSelection.currentIndex))
+ return;
- if (gXFVirtualFolderTerms)
- viewType = nsMsgViewType.eShowVirtualFolderResults;
- else if (gSearchEmailAddress || gVirtualFolderTerms)
- viewType = nsMsgViewType.eShowQuickSearchResults;
- else if (viewType == nsMsgViewType.eShowQuickSearchResults)
- viewType = nsMsgViewType.eShowAllThreads; //override viewType - we don't want to start w/ quick search
- ChangeFolder(realFolder, msgFolder, viewType, viewFlags, sortType, sortOrder);
- if (gVirtualFolderTerms)
- gDBView.viewFolder = msgFolder;
- }
- document.getElementById('tabmail').setTabTitle(null);
- }
- else
- {
- msgWindow.openFolder = null;
- ClearThreadPane();
- }
-
- var startpageenabled = gPrefBranch.getBoolPref("mailnews.start_page.enabled");
- if (gDisplayStartupPage && startpageenabled)
- {
- loadStartPage();
- gDisplayStartupPage = false;
- }
- UpdateMailToolbar("FolderPaneSelectionChange");
-}
-
-function ClearThreadPane()
-{
- if (gDBView) {
- gDBView.close();
- gDBView = null;
- }
+ let folders = GetSelectedMsgFolders();
+ gFolderDisplay.show(folders.length ? folders[0] : null);
}
function IsSpecialFolder(msgFolder, flags, checkAncestors)
{
- if (!msgFolder)
+ if (!msgFolder)
return false;
else if ((msgFolder.flags & flags) == 0)
{
var parentMsgFolder = msgFolder.parentMsgFolder;
return (parentMsgFolder && checkAncestors) ? IsSpecialFolder(parentMsgFolder, flags, true) : false;
}
else {
// the user can set their INBOX to be their SENT folder.
- // in that case, we want this folder to act like an INBOX,
+ // in that case, we want this folder to act like an INBOX,
// and not a SENT folder
const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
return !((flags & nsMsgFolderFlags.SentMail) &&
(msgFolder.flags & nsMsgFolderFlags.Inbox));
}
}
function Undo()
@@ -980,166 +377,10 @@ function Undo()
messenger.undo(msgWindow);
}
function Redo()
{
messenger.redo(msgWindow);
}
-function getSearchTermString(searchTerms)
-{
- var searchIndex;
- var condition = "";
- var count = searchTerms.Count();
- for (searchIndex = 0; searchIndex < count; )
- {
- var term = searchTerms.QueryElementAt(searchIndex++, Components.interfaces.nsIMsgSearchTerm);
-
- if (condition.length > 1)
- condition += ' ';
-
- if (term.matchAll)
- {
- condition = "ALL";
- break;
- }
- condition += (term.booleanAnd) ? "AND (" : "OR (";
- condition += term.termAsString + ')';
- }
- return condition;
-}
-
-function CreateVirtualFolder(newName, parentFolder, searchFolderURIs, searchTerms, searchOnline)
-{
- // ### need to make sure view/folder doesn't exist.
- if (searchFolderURIs && (searchFolderURIs != "") && newName && (newName != ""))
- {
- try
- {
- var newFolder = parentFolder.addSubfolder(newName);
- newFolder.prettyName = newName;
- newFolder.setFlag(Components.interfaces.nsMsgFolderFlags.Virtual);
- var vfdb = newFolder.msgDatabase;
- var searchTermString = getSearchTermString(searchTerms);
- var dbFolderInfo = vfdb.dBFolderInfo;
- // set the view string as a property of the db folder info
- // set the original folder name as well.
- dbFolderInfo.setCharProperty("searchStr", searchTermString);
- dbFolderInfo.setCharProperty("searchFolderUri", searchFolderURIs);
- dbFolderInfo.setBooleanProperty("searchOnline", searchOnline);
-
- vfdb.summaryValid = true;
- vfdb.Close(true);
- parentFolder.NotifyItemAdded(newFolder);
- var accountManager = Components.classes["@mozilla.org/messenger/account-manager;1"].getService(Components.interfaces.nsIMsgAccountManager);
- accountManager.saveVirtualFolders();
- }
- catch(e)
- {
- throw(e); // so that the dialog does not automatically close
- dump ("Exception : creating virtual folder \n");
- }
- }
- else
- {
- dump("no name or nothing selected\n");
- }
-}
-
-var gSearchSession;
-
-var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
-
var gMessengerBundle = null;
-// Datasource search listener -- made global as it has to be registered
-// and unregistered in different functions.
-var gViewSearchListener;
-
-function GetScopeForFolder(folder)
-{
- return folder.server.searchScope;
-}
-
-function setupXFVirtualFolderSearch(folderUrisToSearch, searchTerms, searchOnline)
-{
- const Ci = Components.interfaces;
- var count = new Object;
- var i;
-
- gSearchSession = Components.classes["@mozilla.org/messenger/searchSession;1"]
- .createInstance(Ci.nsIMsgSearchSession);
-
- for (i in folderUrisToSearch)
- {
- var realFolder = GetMsgFolderFromUri(folderUrisToSearch[i]);
- if (!realFolder.isServer)
- gSearchSession.addScopeTerm(!searchOnline ? nsMsgSearchScope.offlineMail : GetScopeForFolder(realFolder), realFolder);
- }
-
- var termsArray = searchTerms.QueryInterface(Components.interfaces.nsISupportsArray);
- const nsIMsgSearchTerm = Components.interfaces.nsIMsgSearchTerm;
- for each (var term in fixIterator(termsArray, nsIMsgSearchTerm)) {
- gSearchSession.appendTerm(term);
- }
-}
-
-function CreateGroupedSearchTerms(searchTermsArray)
-{
- const Ci = Components.interfaces;
- var searchSession = gSearchSession;
- if (!searchSession) {
- searchSession = Components.classes["@mozilla.org/messenger/searchSession;1"]
- .createInstance(Ci.nsIMsgSearchSession);
- }
-
- // create a temporary isupports array to store our search terms
- // since we will be modifying the terms so they work with quick search
- var searchTermsArrayForQS = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
-
- var numEntries = searchTermsArray.Count();
- for (var i = 0; i < numEntries; i++) {
- var searchTerm = searchTermsArray.GetElementAt(i).QueryInterface(Components.interfaces.nsIMsgSearchTerm);
-
- // clone the term, since we might be modifying it
- var searchTermForQS = searchSession.createTerm();
- searchTermForQS.value = searchTerm.value;
- searchTermForQS.attrib = searchTerm.attrib;
- searchTermForQS.arbitraryHeader = searchTerm.arbitraryHeader
- searchTermForQS.op = searchTerm.op;
-
- // mark the first node as a group
- if (i == 0)
- searchTermForQS.beginsGrouping = true;
- else if (i == numEntries - 1)
- searchTermForQS.endsGrouping = true;
-
- // turn the first term to true to work with quick search...
- searchTermForQS.booleanAnd = i ? searchTerm.booleanAnd : true;
-
- searchTermsArrayForQS.AppendElement(searchTermForQS);
- }
- return searchTermsArrayForQS;
-}
-
-function OnLeavingFolder(aFolder)
-{
- try
- {
- // Mark all messages of aFolder as read:
- // We can't use the command controller, because it is already tuned in to the
- // new folder, so we just mimic its behaviour wrt goDoCommand('cmd_markAllRead').
- if (gDBView && gPrefBranch.getBoolPref("mailnews.mark_message_read." + aFolder.server.type))
- {
- gDBView.doCommand(nsMsgViewCommandType.markAllRead);
- }
- }
- catch(e){/* ignore */}
-}
-
-var gViewDebug = false;
-
-function viewDebug(str)
-{
- if (gViewDebug)
- dump(str);
-}
--- a/mail/base/content/composerOverlay.css
+++ b/mail/base/content/composerOverlay.css
@@ -29,16 +29,20 @@
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
+/* Because this sheet is loaded synchronously while the user is waiting for the
+ compose window to appear, it must not @import a ton of other things, and
+ especially must not trigger network access. */
+
.moz-email-headers-table,
.moz-email-headers-table > tbody > tr > th,
.moz-email-headers-table > tbody > tr > td,
blockquote[type=cite] table,
blockquote[type=cite] table > tbody > tr > th,
blockquote[type=cite] table > tbody > tr > td {
border: 1px solid transparent !important;
}
--- a/mail/base/content/extraCustomizeItems.xul
+++ b/mail/base/content/extraCustomizeItems.xul
@@ -1,46 +1,48 @@
<?xml version="1.0"?>
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is Mozilla Communicator client code, released
-# March 31, 1998.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 1998-1999
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# Karsten Düsterloh <mnyromyr@tprac.de>
-# Simon Paquet <bugzilla@babylonsounds.com>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
+<!--
+ - ***** BEGIN LICENSE BLOCK *****
+ - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ -
+ - The contents of this file are subject to the Mozilla Public License Version
+ - 1.1 (the "License"); you may not use this file except in compliance with
+ - the License. You may obtain a copy of the License at
+ - http://www.mozilla.org/MPL/
+ -
+ - Software distributed under the License is distributed on an "AS IS" basis,
+ - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ - for the specific language governing rights and limitations under the
+ - License.
+ -
+ - The Original Code is Mozilla Communicator client code, released
+ - March 31, 1998.
+ -
+ - The Initial Developer of the Original Code is
+ - Netscape Communications Corporation.
+ - Portions created by the Initial Developer are Copyright (C) 1998-1999
+ - the Initial Developer. All Rights Reserved.
+ -
+ - Contributor(s):
+ - Karsten Düsterloh <mnyromyr@tprac.de>
+ - Simon Paquet <bugzilla@babylonsounds.com>
+ -
+ - Alternatively, the contents of this file may be used under the terms of
+ - either the GNU General Public License Version 2 or later (the "GPL"), or
+ - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ - in which case the provisions of the GPL or the LGPL are applicable instead
+ - of those above. If you wish to allow use of your version of this file only
+ - under the terms of either the GPL or the LGPL, and not to allow others to
+ - use your version of this file under the terms of the MPL, indicate your
+ - and other provisions required by the GPL or the LGPL. If you do not delete
+ - the provisions above, a recipient may use your version of this file under
+ - the terms of any one of the MPL, the GPL or the LGPL.
+ -
+ - ***** END LICENSE BLOCK *****
+ -->
<!DOCTYPE overlay [
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd">
%messengerDTD;
<!ENTITY % msgViewPickerDTD SYSTEM "chrome://messenger/locale/msgViewPickerOverlay.dtd" >
%msgViewPickerDTD;
@@ -152,20 +154,26 @@
title="&mailViewsToolbarItem.title;"
align="center"
class="chromeclass-toolbar-additional">
<label id="viewPickerLabel"
value="&viewPicker.label;"
control="viewPicker"
accesskey="&viewPicker.accesskey;"/>
<menulist id="viewPicker" oncommand="ViewChangeByMenuitem(event.target);">
- <menupopup id="viewPickerPopup" onpopupshowing="RefreshViewPopup(this, false);">
- <menuitem id="viewPickerAll" value="0" label="&viewAll.label;"/>
- <menuitem id="viewPickerUnread" value="1" label="&viewUnread.label;"/>
- <menuitem id="viewPickerNotDeleted" value="3" label="&viewNotDeleted.label;"/>
+ <menupopup id="viewPickerPopup" onpopupshowing="RefreshViewPopup(this);">
+ <menuitem id="viewPickerAll" value="0"
+ label="&viewAll.label;"
+ type="radio"/>
+ <menuitem id="viewPickerUnread" value="1"
+ label="&viewUnread.label;"
+ type="radio"/>
+ <menuitem id="viewPickerNotDeleted" value="3"
+ label="&viewNotDeleted.label;"
+ type="radio"/>
<menuseparator id="afterViewPickerUnreadSeparator"/>
<menu id="viewPickerTags" label="&viewTags.label;">
<menupopup id="viewPickerTagsPopup"
class="menulist-menupopup"
onpopupshowing="RefreshTagsPopup(this, true);"/>
</menu>
<menu id="viewPickerCustomViews" label="&viewCustomViews.label;">
<menupopup id="viewPickerCustomViewsPopup"
new file mode 100644
--- /dev/null
+++ b/mail/base/content/folderDisplay.js
@@ -0,0 +1,2075 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ * David Bienvenu <bienvenu@nventure.com>
+ * Siddharth Agarwal <sid.bugzilla@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+Components.utils.import("resource://app/modules/dbViewWrapper.js");
+
+var gFolderDisplay = null;
+var gMessageDisplay = null;
+
+var nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
+
+/**
+ * Abstraction for a widget that (roughly speaking) displays the contents of
+ * folders. The widget belongs to a tab and has a lifetime as long as the tab
+ * that contains it. This class is strictly concerned with the UI aspects of
+ * this; the DBViewWrapper class handles the view details (and is exposed on
+ * the 'view' attribute.)
+ *
+ * The search window subclasses this into the SearchFolderDisplayWidget rather
+ * than us attempting to generalize everything excessively. This is because
+ * we hate the search window and don't want to clutter up this code for it.
+ * The standalone message display window also subclasses us; we do not hate it,
+ * but it's not invited to our birthday party either.
+ * For reasons of simplicity and the original order of implementation, this
+ * class does alter its behavior slightly for the benefit of the standalone
+ * message window. If no tab info is provided, we avoid touching tabmail
+ * (which is good, because it won't exist!) And now we guard against treeBox
+ * manipulations...
+ */
+function FolderDisplayWidget(aTabInfo, aMessageDisplayWidget) {
+ this._tabInfo = aTabInfo;
+
+ /// If the folder does not get handled by the DBViewWrapper, stash it here.
+ /// ex: when isServer is true.
+ this._nonViewFolder = null;
+
+ this.view = new DBViewWrapper(this);
+ this.messageDisplay = aMessageDisplayWidget;
+ this.messageDisplay.folderDisplay = this;
+
+ /**
+ * The XUL tree node, as retrieved by getDocumentElementById. The caller is
+ * responsible for setting this.
+ */
+ this.tree = null;
+ /**
+ * The nsITreeBoxObject on the XUL tree node, accessible from this.tree as
+ * this.tree.boxObject and QueryInterfaced as such. The caller is
+ * responsible for setting this.
+ */
+ this.treeBox = null;
+
+ /**
+ * The nsIMsgWindow corresponding to the window that holds us. There is only
+ * one of these per tab. The caller is responsible for setting this.
+ */
+ this.msgWindow = null;
+ /**
+ * The nsIMessenger instance that corresponds to our tab/window. We do not
+ * use this ourselves, but are responsible for using it to update the
+ * global |messenger| object so that our tab maintains its own undo and
+ * navigation history. At some point we might touch it for those reasons.
+ */
+ this.messenger = null;
+ this.threadPaneCommandUpdater = this;
+
+ /**
+ * Flag to expose whether all messages are loaded or not. Set by
+ * onAllMessagesLoaded.
+ */
+ this._allMessagesLoaded = false;
+
+ /**
+ * Save the top row displayed when we go inactive, restore when we go active,
+ * nuke it when we destroy the view.
+ */
+ this._savedFirstVisibleRow = null;
+ /** the next view index to select once the delete completes */
+ this._nextViewIndexAfterDelete = null;
+ /**
+ * Track when a mass move is in effect (we get told by hintMassMoveStarting,
+ * and hintMassMoveCompleted) so that we can avoid deletion-triggered
+ * moving to _nextViewIndexAfterDelete until the mass move completes.
+ */
+ this._massMoveActive = false;
+
+ /**
+ * Used by pushNavigation to queue a navigation request for when we enter the
+ * next folder; onAllMessagesLoaded is the one that processes it.
+ */
+ this._pendingNavigation = null;
+
+ this._active = false;
+ /**
+ * A list of methods to call on 'this' object when we are next made active.
+ * This list is populated by calls to |_notifyWhenActive| when we are
+ * not active at the moment.
+ */
+ this._notificationsPendingActivation = [];
+
+ let dummyDOMNode = document.getElementById('mail-toolbox');
+ /**
+ * Create a fake tree box object for if/when this folder is in the background.
+ * We need to give it a bogus DOM object to send events to, so we choose the
+ * mail-toolbox, who is hopefully unlikely to take offense.
+ */
+ this._fakeTreeBox = dummyDOMNode ?
+ new FakeTreeBoxObject(dummyDOMNode.boxObject) : null;
+
+ this._mostRecentSelectionCounts = [];
+ this._mostRecentCurrentIndices = [];
+}
+FolderDisplayWidget.prototype = {
+ /**
+ * @return the currently displayed folder. This is just proxied from the
+ * view wrapper.
+ */
+ get displayedFolder() {
+ return this._nonViewFolder || this.view.displayedFolder;
+ },
+
+ /**
+ * @return the nsITreeSelection object for our tree view if there is one,
+ * null otherwise.
+ */
+ get treeSelection() {
+ if (this.view.dbView)
+ return this.view.dbView.selection;
+ else
+ return null;
+ },
+
+ /**
+ * Number of headers to tell the message database to cache when we enter a
+ * folder. This value is being propagated from legacy code which provided
+ * no explanation for its choice.
+ *
+ * We definitely want the header cache size to be larger than the number of
+ * rows that can be displayed on screen simultaneously.
+ */
+ PERF_HEADER_CACHE_SIZE: 100,
+
+ /**
+ * An optional list where each item is an object with the following
+ * attributes sufficient to re-establish the selected items even in the face
+ * of folder renaming.
+ * - messageId: The value of the message's message-id header.
+ *
+ * That's right, we only save the message-id header value. This is arguably
+ * overkill and ambiguous in the face of duplicate messages, but it's the
+ * most persistent/reliable thing we have without gloda.
+ * Using the view index was ruled out because it is hardly stable. Using the
+ * message key alone is insufficient for cross-folder searches. Using a
+ * folder identifier and message key is insufficent for local folders in the
+ * face of compaction, let alone complexities where the folder name may
+ * change due to renaming/moving. Which means we eventually need to fall
+ * back to message-id anyways. Feel free to add in lots of complexity if
+ * you actually write unit tests for all the many possible cases.
+ * Additional justification is that selection saving/restoration should not
+ * happen all that frequently. A nice freebie is that message-id is
+ * definitely persistable.
+ */
+ _savedSelection: null,
+
+ /**
+ * Save the current view selection for when we the view is getting destroyed
+ * or otherwise re-ordered in such a way that the nsITreeSelection will lose
+ * track of things (because it just has a naive view-index 'view' of the
+ * world.) We just save each message's message-id header. This is overkill
+ * and ambiguous in the face of duplicate messages (and expensive to
+ * restore), but is also the most reliable option for this use case.
+ */
+ _saveSelection: function FolderDisplayWidget_saveSelection() {
+ this._savedSelection = [{messageId: msgHdr.messageId} for each
+ ([, msgHdr] in Iterator(this.selectedMessages))];
+ },
+
+ /**
+ * Clear the saved selection.
+ */
+ _clearSavedSelection: function FolderDisplayWidget_clearSavedSelection() {
+ this._savedSelection = null;
+ },
+
+ /**
+ * Restore the view selection if we have a saved selection. We must be
+ * active!
+ *
+ * @return true if we were able to restore the selection and there was
+ * a selection, false if there was no selection (anymore).
+ */
+ _restoreSelection: function FolderDisplayWidget_restoreSelection() {
+ if (!this._savedSelection || !this._active)
+ return false;
+
+ // translate message IDs back to messages. this is O(s(m+n)) where:
+ // - s is the number of messages saved in the selection
+ // - m is the number of messages in the view (from findIndexOfMsgHdr)
+ // - n is the number of messages in the underlying folders (from
+ // DBViewWrapper.getMsgHdrForMessageID).
+ // which ends up being O(sn)
+ var msgHdr;
+ let messages =
+ [msgHdr for each
+ ([, savedInfo] in Iterator(this._savedSelection)) if
+ ((msgHdr = this.view.getMsgHdrForMessageID(savedInfo.messageId)))];
+
+ this.selectMessages(messages, true);
+ this._savedSelection = null;
+
+ return this.selectedCount != 0;
+ },
+
+ /**
+ * Maps column ids to functions that test whether the column is legal for
+ * display for the folder. Each function should expect a DBViewWrapper
+ * instance as its argument. The intent is that the various helper
+ * properties like isMailFolder/isIncomingFolder/isOutgoingFolder allow the
+ * constraint to be expressed concisely. If a helper does not exist, add
+ * one! (If doing so is out of reach, than access viewWrapper.displayedFolder
+ * to get at the nsIMsgFolder.)
+ * If a column does not have a function, it is assumed to be legal for display
+ * in all cases.
+ */
+ COLUMN_LEGALITY_TESTERS: {
+ // Only show 'Received' column for e-mails. For newsgroup messages, the
+ // 'Date' header is as reliable as an e-mail's 'Received' header, as it is
+ // replaced with the news server's (more reliable) date.
+ receivedCol: function (viewWrapper) {
+ return viewWrapper.isMailFolder && !viewWrapper.isOutgoingFolder;
+ },
+ // senderCol = From. You only care in incoming folders.
+ senderCol: function (viewWrapper) {
+ return viewWrapper.isIncomingFolder;
+ },
+ // recipient = To. You only care in outgoing folders.
+ recipientCol: function (viewWrapper) {
+ return viewWrapper.isOutgoingFolder;
+ },
+ // Only show the location column for non-single-folder results
+ locationCol: function(viewWrapper) {
+ return !viewWrapper.isSingleFolder;
+ },
+ },
+
+ /**
+ * If we determine that a column is illegal but was displayed, use this
+ * mapping to find suggested legal alternatives. This basically exists
+ * just to flip-flop between senderCol and recipientCol.
+ */
+ COLUMN_LEGAL_ALTERNATIVES: {
+ // swap between sender and recipient
+ senderCol: ["recipientCol"],
+ recipientCol: ["senderCol"],
+ // if we nuke received, put back date...
+ receivedCol: ["dateCol"],
+ },
+
+ /**
+ * Columns to display whenever we can. This is currently a bit of a hack to
+ * always show the location column when relevant. Arguably, it would be
+ * better to use this as a default for a folder you've never been in and
+ * the rest of the time we restore the last column set for that folder from
+ * properties.
+ */
+ COLUMNS_DISPLAY_WHEN_POSSIBLE: ["locationCol"],
+
+ /**
+ * Update the displayed columns so that:
+ * - Only legal columns (per COLUMN_LEGALITY_TESTERS) are displayed.
+ * - Alternatives to now-illegal columns may be displayed.
+ */
+ updateColumns: function() {
+ // Keep a list of columns we might want to make show up if they are not
+ // illegal.
+ let legalize = this.COLUMNS_DISPLAY_WHEN_POSSIBLE.concat();
+
+ // figure out who is illegal and make them go away
+ for (let [colId, legalityFunc] in Iterator(this.COLUMN_LEGALITY_TESTERS)) {
+ let column = document.getElementById(colId);
+ // The search window does not have all the columns we know about, bail in
+ // such cases.
+ if (!column)
+ continue;
+ let legal = legalityFunc(this.view);
+
+ if (!legal) {
+ let isHidden = column.getAttribute("hidden") == "true";
+ // If it wasn't hidden, consider making its alternatives visible in the
+ // next pass.
+ if (!isHidden && (colId in this.COLUMN_LEGAL_ALTERNATIVES))
+ legalize = legalize.concat(this.COLUMN_LEGAL_ALTERNATIVES[colId]);
+ // but definitely hide the heck out of it right now
+ column.setAttribute("hidden", true);
+ column.setAttribute("ignoreincolumnpicker", true);
+ }
+ else {
+ column.removeAttribute("ignoreincolumnpicker");
+ }
+ }
+ // If we have any columns we should consider making visible because they are
+ // alternatives to columns that became illegal, uh, do that.
+ for each (let [, colId] in Iterator(legalize)) {
+ let column = document.getElementById(colId);
+ let isLegal = column.getAttribute("ignoreincolumnpicker") != "true";
+ let isHidden = column.getAttribute("hidden") == "true";
+ if (isLegal && isHidden)
+ column.removeAttribute("hidden");
+ }
+ },
+
+ /**
+ * @param aColumnMap an object where the attribute names are column ids and
+ * the values are a boolean indicating whether the column should be
+ * visible or not. If a column is not in the map, it is assumed that it
+ * should be hidden.
+ */
+ setVisibleColumns: function(aColumnMap) {
+ let cols = document.getElementById("threadCols");
+ let colChildren = cols.children;
+
+ // because the splitter correspondence can be confusing and tricky, let's
+ // build a list of the nodes ordered by their ordinal
+ let ordinalOrdered = [], iKid;
+ for (iKid = 0; iKid < colChildren.length; iKid++)
+ ordinalOrdered.push(null);
+
+ for (iKid = 0; iKid < colChildren.length; iKid++) {
+ let colChild = colChildren[iKid];
+ let ordinal = colChild.getAttribute("ordinal") - 1;
+ ordinalOrdered[ordinal] = colChild;
+ }
+
+ function twiddleAround(index, makeHidden) {
+ if (index + 1 < ordinalOrdered.length) {
+ let nexty = ordinalOrdered[index+1];
+ if (nexty) {
+ let isHidden = nexty.getAttribute("hidden") == "true";
+ if (isHidden != makeHidden) {
+ nexty.setAttribute("hidden", true);
+ return;
+ }
+ }
+ }
+ if (index - 1 > 0) {
+ let prevy = ordinalOrdered[index-1];
+ if (prevy) {
+ let isHidden = prevy.getAttribute("hidden") == "true";
+ if (isHidden != makeHidden) {
+ prevy.setAttribute("hidden", true);
+ return;
+ }
+ }
+ }
+ }
+
+ for (iKid = 0; iKid < ordinalOrdered.length; iKid++) {
+ let colChild = ordinalOrdered[iKid];
+ if (colChild == null)
+ continue;
+
+ if (colChild.tagName == "treecol") {
+ if (colChild.id in aColumnMap) {
+ // only need to do something if currently hidden
+ if (colChild.getAttribute("hidden") == "true") {
+ colChild.removeAttribute("hidden");
+ twiddleAround(iKid, false);
+ }
+ }
+ else {
+ // only need to do something if currently visible
+ if (colChild.getAttribute("hidden") != "true") {
+ colChild.setAttribute("hidden", true);
+ twiddleAround(iKid, true);
+ }
+ }
+ }
+ }
+ },
+
+ _savedColumnStates: null,
+
+ /**
+ * For now, just save the visible columns into a dictionary for use in a
+ * subsequent call to setVisibleColumns. This does not do anything about
+ * re-arranging columns.
+ */
+ saveColumnStates: function() {
+ // In the actual nsITreeColumn, the index property indicates the column
+ // number. This column number is a 0-based index with no gaps; it only
+ // increments the number each time it sees a column.
+ // However, this is subservient to the 'ordinal' property which
+ // defines the _apparent content sequence_ provided by GetNextSibling.
+ // The underlying content ordering is still the same, which is how
+ // restoreNaturalOrder can reset things to their XUL definition sequence.
+ // The 'ordinal' stuff works because nsBoxFrame::RelayoutChildAtOrdinal
+ // messes with the sibling relationship.
+ // Ordinals are 1-based. restoreNaturalOrder apparently is dumb and does
+ // not know this, although the ordering is relative so it doesn't actually
+ // matter. The annoying splitters do have ordinals, and live between
+ // tree columns. The splitters adjacent to a tree column do not need to
+ // have any 'ordinal' relationship, although it would appear user activity
+ // tends to move them around in a predictable fashion with oddness involved
+ // at the edges.
+ // Changes to the ordinal attribute should take immediate effect in terms of
+ // sibling relationship, but will merely invalidate the columns rather than
+ // cause a re-computaiton of column relationships every time.
+ // restoreNaturalOrder invalidates the tree when it is done re-ordering; I'm
+ // not sure that's entirely necessary...
+
+ let visibleMap = {};
+
+ let cols = document.getElementById("threadCols");
+ let colChildren = cols.children;
+ for (let iKid = 0; iKid < colChildren.length; iKid++) {
+ let colChild = colChildren[iKid];
+ if (colChild.getAttribute("hidden") != "true")
+ visibleMap[colChild.id] = true;
+ }
+
+ this._savedColumnStates = visibleMap;
+ },
+
+ /**
+ * Restores the visible columns saved by saveColumnStates. Some day, in the
+ * future we might do something about positions and the like. But we don't
+ * currently.
+ */
+ restoreColumnStates: function () {
+ if (this._savedColumnStates) {
+ this.setVisibleColumns(this._savedColumnStates);
+ this._savedColumnStates = null;
+ }
+ },
+
+ showFolderUri: function FolderDisplayWidget_showFolderUri(aFolderURI) {
+ return this.show(GetMsgFolderFromUri(aFolderURI));
+ },
+
+ /**
+ * Invoked by showFolder when it turns out the folder is in fact a server.
+ */
+ _showServer: function FolderDisplayWidget__showServer() {
+ // currently nothing to do. makeActive handles everything for us (because
+ // what is displayed needs to be re-asserted each time we are activated
+ // too.)
+ },
+
+ /**
+ * Select a folder for display.
+ *
+ * @param aFolder The nsIMsgDBFolder to display.
+ */
+ show: function FolderDisplayWidget_show(aFolder) {
+ if (aFolder == null) {
+ this._nonViewFolder = null;
+ this.view.close();
+ }
+ else if (aFolder instanceof Components.interfaces.nsIMsgFolder) {
+ if (aFolder.isServer) {
+ this._nonViewFolder = aFolder;
+ this._showServer();
+ this.view.close();
+ // A server is fully loaded immediately, for now. (When we have the
+ // account summary, we might want to change this to wait for the page
+ // load to complete.)
+ this._allMessagesLoaded = true;
+ }
+ else {
+ this._nonViewFolder = null;
+ this.view.open(aFolder);
+ }
+ }
+ // it must be a synthetic view
+ else {
+ this.view.openSynthetic(aFolder);
+ }
+ if (this._active)
+ this.makeActive();
+
+ if (this._tabInfo)
+ document.getElementById('tabmail').setTabTitle(this._tabInfo);
+ },
+
+ /**
+ * Clone an existing view wrapper as the basis for our display.
+ */
+ cloneView: function FolderDisplayWidget_cloneView(aViewWrapper) {
+ this.view = aViewWrapper.clone(this);
+ // generate a view created notification; this will cause us to do the right
+ // thing in terms of associating the view with the tree and such.
+ this.onCreatedView();
+ if (this._active)
+ this.makeActive();
+ },
+
+ /**
+ * Close resources associated with the currently displayed folder because you
+ * no longer care about this FolderDisplayWidget.
+ */
+ close: function FolderDisplayWidget_close() {
+ // Mark ourselves as inactive without doing any of the hard work of becoming
+ // inactive. This saves us from trying to update things as they go away.
+ this._active = false;
+ // Tell the message display to close itself too. We do this before we do
+ // anything else because closing the view could theoretically propagate
+ // down to the message display and we don't want it doing anything it
+ // doesn't have to do.
+ this.messageDisplay._close();
+
+ this.view.close();
+ this.messenger.setWindow(null, null);
+ this.messenger = null;
+ this._fakeTreeBox = null;
+ },
+
+ /* =============================== */
+ /* ===== IDBViewWrapper Listener ===== */
+ /* =============================== */
+
+ /**
+ * @return true if the mail view picker is visible. This affects whether the
+ * DBViewWrapper will actually use the persisted mail view or not.
+ */
+ get shouldUseMailViews() {
+ return ViewPickerBinding.isVisible;
+ },
+
+ /**
+ * Let the viewWrapper know if we should defer message display because we
+ * want the user to connect to the server first so password authentication
+ * can occur.
+ *
+ * @return true if the folder should be shown immediately, false if we should
+ * wait for updateFolder to complete.
+ */
+ get shouldDeferMessageDisplayUntilAfterServerConnect() {
+ let passwordPromptRequired = false;
+
+ if (gPrefBranch.getBoolPref("mail.password_protect_local_cache"))
+ passwordPromptRequired =
+ this.view.displayedFolder.server.passwordPromptRequired;
+
+ return passwordPromptRequired;
+ },
+
+ /**
+ * Let the viewWrapper know if it should mark the messages read when leaving
+ * the provided folder.
+ *
+ * @return true if the preference is set for the folder's server type.
+ */
+ shouldMarkMessagesReadOnLeavingFolder:
+ function FolderDisplayWidget_crazyMarkOnReadChecker (aMsgFolder) {
+ return gPrefBranch.getBoolPref("mailnews.mark_message_read." +
+ aMsgFolder.server.type);
+ },
+
+ /**
+ * The view wrapper tells us when it starts loading a folder, and we set the
+ * cursor busy. Setting the cursor busy on a per-tab basis is us being
+ * nice to the future. Loading a folder is a blocking operation that is going
+ * to make us unresponsive and accordingly make it very hard for the user to
+ * change tabs.
+ */
+ onFolderLoading: function(aFolderLoading) {
+ if (this._tabInfo)
+ document.getElementById("tabmail").setTabBusy(this._tabInfo,
+ aFolderLoading);
+ },
+
+ /**
+ * The view wrapper tells us when a search is active, and we mark the tab as
+ * thinking so the user knows something is happening. 'Searching' in this
+ * case is more than just a user-initiated search. Virtual folders / saved
+ * searches, mail views, plus the more obvious quick search are all based off
+ * of searches and we will receive a notification for them.
+ */
+ onSearching: function(aIsSearching) {
+ // getDocumentElements() sets gSearchBundle
+ getDocumentElements();
+ if (this._tabInfo)
+ document.getElementById("tabmail").setTabThinking(
+ this._tabInfo,
+ aIsSearching && gSearchBundle.getString("searchingMessage"));
+ },
+
+ /**
+ * Things we do on creating a view:
+ * - notify the observer service so that custom column handler providers can
+ * add their custom columns to our view.
+ */
+ onCreatedView: function FolderDisplayWidget_onCreatedView() {
+ // All of our messages are not displayed if the view was just created. We
+ // will get an onAllMessagesLoaded nearly immediately if this is a local
+ // folder where view creation is synonymous with having all messages.
+ this._allMessagesLoaded = false;
+ this.messageDisplay.onCreatedView();
+ this._notifyWhenActive(this._activeCreatedView);
+ },
+ _activeCreatedView: function() {
+ gDBView = this.view.dbView;
+
+ // A change in view may result in changes to sorts, the view menu, etc.
+ // Do this before we 'reroot' the dbview.
+ this._updateThreadDisplay();
+
+ // this creates a new selection object for the view.
+ if (this.treeBox)
+ this.treeBox.view = this.view.dbView;
+
+ let ObserverService =
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ // The data payload used to be viewType + ":" + viewFlags. We no longer
+ // do this because we already have the implied contract that gDBView is
+ // valid at the time we generate the notification. In such a case, you
+ // can easily get that information from the gDBView. (The documentation
+ // on creating a custom column assumes gDBView.)
+ ObserverService.notifyObservers(this.displayedFolder,
+ "MsgCreateDBView", "");
+ },
+
+ /**
+ * If our view is being destroyed and it is coming back, we want to save the
+ * current selection so we can restore it when the view comes back.
+ */
+ onDestroyingView: function FolderDisplayWidget_onDestroyingView(
+ aFolderIsComingBack) {
+ // try and persist the selection's content if we can
+ if (this._active) {
+ if (aFolderIsComingBack)
+ this._saveSelection();
+ else
+ this._clearSavedSelection();
+ gDBView = null;
+ }
+
+ // if we have no view, no messages could be loaded.
+ this._allMessagesLoaded = false;
+
+ // but the actual tree view selection (based on view indicies) is a goner no
+ // matter what, make everyone forget.
+ this.view.dbView.selection = null;
+ this._savedFirstVisibleRow = null;
+ this._nextViewIndexAfterDelete = null;
+ // although the move may still be active, its relation to the view is moot.
+ this._massMoveActive = false;
+
+ // Anything pending needs to get cleared out; the new view and its related
+ // events will re-schedule anything required or simply run it when it
+ // has its initial call to makeActive compelled.
+ this._notificationsPendingActivation = [];
+
+ // and the message display needs to forget
+ this.messageDisplay.onDestroyingView(aFolderIsComingBack);
+ },
+ /**
+ * We are entering the folder for display, set the header cache size.
+ */
+ onDisplayingFolder: function FolderDisplayWidget_onDisplayingFolder() {
+ let msgDatabase = this.view.displayedFolder.msgDatabase;
+ if (msgDatabase) {
+ msgDatabase.resetHdrCacheSize(this.PERF_HEADER_CACHE_SIZE);
+ }
+
+ // the quick-search gets nuked when we show a new folder
+ ClearQSIfNecessary();
+ // update the quick-search relative to whether it's incoming/outgoing
+ onSearchFolderTypeChanged(this.view.isOutgoingFolder);
+
+ if (this.active)
+ this.makeActive();
+ },
+
+ /**
+ * Notification from DBViewWrapper that it is closing the folder. This can
+ * happen for reasons other than our own 'close' method closing the view.
+ * For example, user deletion of the folder or underlying folder closes it.
+ */
+ onLeavingFolder: function FolderDisplayWidget_onLeavingFolder() {
+ // Keep the msgWindow's openFolder up-to-date; it powers nsMessenger's
+ // concept of history so that it can bring you back to the actual folder
+ // you were looking at, rather than just the underlying folder.
+ if (this._active)
+ msgWindow.openFolder = null;
+ },
+
+ /**
+ * Indictes whether we are done loading the messages that should be in this
+ * folder. This is being surfaced for testing purposes, but could be useful
+ * to other code as well. But don't poll this property; ask for an event
+ * that you can hook.
+ */
+ get allMessagesLoaded FolderDisplayWidget_get_allMessagesLoaded() {
+ return this._allMessagesLoaded;
+ },
+
+ /**
+ * Things to do once all the messages that should show up in a folder have
+ * shown up. For a real folder, this happens when the folder is entered.
+ * For a virtual folder, this happens when the search completes.
+ *
+ * What we do:
+ * - Any scrolling required!
+ */
+ onAllMessagesLoaded: function FolderDisplayWidget_onAllMessagesLoaded() {
+ this._allMessagesLoaded = true;
+ this._notifyWhenActive(this._activeAllMessagesLoaded);
+ },
+ _activeAllMessagesLoaded:
+ function FolderDisplayWidget__activeAllMessagesLoaded() {
+ // - restore selection
+ // Attempt to restore the selection (if we saved it because the view was
+ // being destroyed or otherwise manipulated in a fashion that the normal
+ // nsTreeSelection would be unable to handle.)
+ if (this._restoreSelection()) {
+ this.ensureRowIsVisible(this.view.dbView.viewIndexForFirstSelectedMsg);
+ return;
+ }
+
+ // - pending navigation from pushNavigation (probably spacebar triggered)
+ if (this._pendingNavigation) {
+ // Move it to a local and clear the state in case something bad happens.
+ // (We don't want to swallow the exception.)
+ let pendingNavigation = this._pendingNavigation;
+ this._pendingNavigation = null;
+ this.navigate.apply(this, pendingNavigation);
+ return;
+ }
+
+ // - new messages
+ // if configured to scroll to new messages, try that
+ if (gPrefBranch.getBoolPref("mailnews.scroll_to_new_message") &&
+ this.navigate(nsMsgNavigationType.firstNew, /* select */ false))
+ return;
+
+ // - last selected message
+ // if configured to load the last selected message (this is currently more
+ // persistent than our saveSelection/restoreSelection stuff), and the view
+ // is backed by a single underlying folder (the only way having just a
+ // message key works out), try that
+ if (gPrefBranch.getBoolPref("mailnews.remember_selected_message") &&
+ this.view.isSingleFolder) {
+ // use the displayed folder; nsMsgDBView goes to the effort to save the
+ // state to the viewFolder, so this is the correct course of action.
+ let lastLoadedMessageKey = this.view.displayedFolder.lastMessageLoaded;
+ if (lastLoadedMessageKey != nsMsgKey_None) {
+ this.view.dbView.selectMsgByKey(lastLoadedMessageKey);
+ // The message key may not be present in the view for a variety of
+ // reasons. Beyond message deletion, it simply may not match the
+ // active mail view or quick search, for example.
+ if (this.view.dbView.numSelected) {
+ this.ensureRowIsVisible(
+ this.view.dbView.viewIndexForFirstSelectedMsg);
+ return;
+ }
+ }
+ }
+
+ // - towards the newest messages, but don't select
+ if (this.view.isSortedAscending && this.view.sortImpliesTemporalOrdering &&
+ this.navigate(nsMsgNavigationType.lastMessage, /* select */ false))
+ return;
+
+ // - to the top, the coliseum
+ this.ensureRowIsVisible(0);
+ },
+
+ /**
+ * The DBViewWrapper tells us when someone (possibly the wrapper itself)
+ * changes the active mail view so that we can kick the UI to update.
+ */
+ onMailViewChanged: function FolderDisplayWidget_onMailViewChanged() {
+ // only do this if we're currently active. no need to queue it because we
+ // always update the mail view whenever we are made active.
+ if (this.active) {
+ let event = document.createEvent("datacontainerevents");
+ // you cannot cancel a view change!
+ event.initEvent("MailViewChanged", false, false);
+ //event.setData("folderDisplay", this);
+ window.dispatchEvent(event);
+ }
+ },
+
+ /**
+ * Just the sort or threading was changed, without changing other things. We
+ * will not get this notification if the view was re-created, for example.
+ */
+ onSortChanged: function FolderDisplayWidget_onSortChanged() {
+ if (this.active)
+ UpdateSortIndicators(this.view.primarySortType,
+ this.view.primarySortOrder);
+ },
+
+ /**
+ * Messages (that may have been displayed) have been removed; this may impact
+ * our message selection. If we saw this coming, then
+ * this._nextViewIndexAfterDelete should know what view index we should
+ * select next. If we didn't see this coming, the cause is likely an
+ * explicit deletion in another tab/window.
+ * Because the nsMsgDBView is on top of things, it will already have called
+ * summarizeSelection as a result of the changes to the message display.
+ * So our job here is really just to try and potentially improve on the
+ * default selection logic.
+ */
+ onMessagesRemoved: function FolderDisplayWidget_onMessagesRemoved() {
+ // - we saw this coming
+ let rowCount = this.view.dbView.rowCount;
+ if (!this._massMoveActive && (this._nextViewIndexAfterDelete != null)) {
+ // adjust the index if it is after the last row...
+ // (this can happen if the "mail.delete_matches_sort_order" pref is not
+ // set and the message is the last message in the view.)
+ if (this._nextViewIndexAfterDelete >= rowCount)
+ this._nextViewIndexAfterDelete = rowCount - 1;
+ // just select the index and get on with our lives
+ this.selectViewIndex(this._nextViewIndexAfterDelete);
+ this._nextViewIndexAfterDelete = null;
+ return;
+ }
+
+ // - surprise!
+ // A deletion happened to our folder.
+ let treeSelection = this.treeSelection;
+ // we can't fix the selection if we have no selection
+ if (!treeSelection)
+ return;
+
+ // For reasons unknown (but theoretically knowable), sometimes the selection
+ // object will be invalid. At least, I've reliably seen a selection of
+ // [0, 0] with 0 rows. If that happens, we need to fix up the selection
+ // here.
+ if (rowCount == 0 && treeSelection.count)
+ // nsTreeSelection does't generate an event if we use clearRange, so use
+ // that to avoid spurious events, given that we are going to definitely
+ // trigger a change notification below.
+ treeSelection.clearRange(0, 0);
+
+ // Check if we now no longer have a selection, but we had exactly one
+ // message selected previously. If we did, then try and do some
+ // 'persistence of having a thing selected'.
+ if (treeSelection.count == 0 &&
+ this._mostRecentSelectionCounts.length > 1 &&
+ this._mostRecentSelectionCounts[1] == 1 &&
+ this._mostRecentCurrentIndices[1] != -1) {
+ let targetIndex = this._mostRecentCurrentIndices[1];
+ if (targetIndex >= rowCount)
+ targetIndex = rowCount - 1;
+ this.selectViewIndex(targetIndex);
+ return;
+ }
+
+ // Otherwise, just tell the view that things have changed so it can update
+ // itself to the new state of things.
+ // tell the view that things have changed so it can update itself suitably.
+ if (this.view.dbView)
+ this.view.dbView.selectionChanged();
+ },
+
+ /**
+ * Messages were not actually removed, but we were expecting that they would
+ * be. Clean-up what onMessagesRemoved would have cleaned up, namely the
+ * next view index to select.
+ */
+ onMessageRemovalFailed:
+ function FolderDisplayWidget_onMessageRemovalFailed() {
+ this._nextViewIndexAfterDelete = null;
+ },
+
+ /**
+ * Update the status bar to reflect our exciting message counts.
+ */
+ onMessageCountsChanged: function FolderDisplayWidget_onMessageCountsChaned() {
+ if (this.active)
+ UpdateStatusMessageCounts(this.displayedFolder);
+ },
+
+ /* ===== End IDBViewWrapperListener ===== */
+
+ /* ================================== */
+ /* ===== nsIMsgDBViewCommandUpdater ===== */
+ /* ================================== */
+
+ /**
+ * This gets called when the selection changes AND !suppressCommandUpdating
+ * AND (we're not removing a row OR we are now out of rows).
+ * In response, we update the toolbar.
+ */
+ updateCommandStatus: function FolderDisplayWidget_updateCommandStatus() {
+ UpdateMailToolbar("FolderDisplayWidget command updater notification");
+ },
+
+ /**
+ * This gets called by nsMsgDBView::UpdateDisplayMessage following a call
+ * to nsIMessenger.OpenURL to kick off message display OR (UDM gets called)
+ * by nsMsgDBView::SelectionChanged in lieu of loading the message because
+ * mSupressMsgDisplay.
+ * In other words, we get notified immediately after the process of displaying
+ * a message triggered by the nsMsgDBView happens. We get some arguments
+ * that are display optimizations for historical reasons (as usual).
+ *
+ * Things this makes us want to do:
+ * - Set the tab title, perhaps. (If we are a message display.)
+ * - Update message counts, because things might have changed, why not.
+ * - Update some toolbar buttons, why not.
+ *
+ * @param aFolder The display/view folder, as opposed to the backing folder.
+ * @param aSubject The subject with "Re: " if it's got one, which makes it
+ * notably different from just directly accessing the message header's
+ * subject.
+ * @param aKeywords The keywords, which roughly translates to message tags.
+ */
+ displayMessageChanged: function FolderDisplayWidget_displayMessageChanged(
+ aFolder, aSubject, aKeywords) {
+ UpdateMailToolbar("FolderDisplayWidget displayed message changed");
+ let viewIndex = this.view.dbView.currentlyDisplayedMessage;
+ let msgHdr = (viewIndex != nsMsgViewIndex_None) ?
+ this.view.dbView.getMsgHdrAt(viewIndex) : null;
+ this.messageDisplay.onDisplayingMessage(msgHdr);
+
+ // Although deletes should now be so fast that the user has no time to do
+ // anything, treat the user explicitly choosing to display a different
+ // message as invalidating the choice we automatically made for them when
+ // they initiated the message delete / move. (bug 243532)
+ // Note: legacy code used to check whether the message being displayed was
+ // the one being deleted, so it didn't erroneously clear the next message
+ // to display (bug 183394). This is not a problem for us because we hook
+ // our notification when the message load is initiated, rather than when
+ // the message completes loading.
+ this._nextViewIndexAfterDelete = null;
+ },
+
+ /**
+ * This gets called as a hint that the currently selected message is junk and
+ * said junked message is going to be moved out of the current folder. The
+ * legacy behaviour is to retrieve the msgToSelectAfterDelete attribute off
+ * the db view, stashing it for benefit of the code that gets called when a
+ * message move/deletion is completed so that we can trigger its display.
+ */
+ updateNextMessageAfterDelete:
+ function FolderDisplayWidget_updateNextMessageAfterDelete() {
+ this.hintAboutToDeleteMessages();
+ },
+
+ /**
+ * The most recent currentIndexes on the selection (from the last time
+ * summarizeSelection got called). We use this in onMessagesRemoved if
+ * we get an unexpected notification.
+ * We keep a maximum of 2 entries in this list.
+ */
+ _mostRecentCurrentIndices: undefined, // initialized in constructor
+ /**
+ * The most recent counts on the selection (from the last time
+ * summarizeSelection got called). We use this in onMessagesRemoved if
+ * we get an unexpected notification.
+ * We keep a maximum of 2 entries in this list.
+ */
+ _mostRecentSelectionCounts: undefined, // initialized in constructor
+
+ /**
+ * Always called by the db view when the selection changes in
+ * SelectionChanged. This event will come after the notification to
+ * displayMessageChanged (if one happens), and before the notification to
+ * updateCommandStatus (if one happens).
+ */
+ summarizeSelection: function FolderDisplayWidget_summarizeSelection() {
+ // save the current index off in case the selection gets deleted out from
+ // under us and we want to have persistence of actually-having-something
+ // selected.
+ let treeSelection = this.treeSelection;
+ if (treeSelection) {
+ this._mostRecentCurrentIndices.unshift(treeSelection.currentIndex);
+ this._mostRecentCurrentIndices.splice(2);
+ this._mostRecentSelectionCounts.unshift(treeSelection.count);
+ this._mostRecentSelectionCounts.splice(2);
+ }
+ return this.messageDisplay.onSelectedMessagesChanged();
+ },
+
+ /* ===== End nsIMsgDBViewCommandUpdater ===== */
+
+ /* ===== Hints from the command infrastructure ===== */
+
+ /**
+ * doCommand helps us out by telling us when it is telling the view to delete
+ * some messages. Ideally it should go through us / the DB View Wrapper to
+ * kick off the delete in the first place, but that's a thread I don't want
+ * to pull on right now.
+ * We use this hint to figure out the next message to display once the
+ * deletion completes. We do this before the deletion happens because the
+ * selection is probably going away (except in the IMAP delete model), and it
+ * might be too late to figure this out after the deletion happens.
+ * Our automated complement (that calls us) is updateNextMessageAfterDelete.
+ */
+ hintAboutToDeleteMessages:
+ function FolderDisplayWidget_hintAboutToDeleteMessages() {
+ // If there is a right click going on, then the possibilities are:
+ // 1) The user right-clicked in the selection. In this case, the selection
+ // is maintained. This holds true for one or multiple messages.
+ // 2) The user right-clicked outside the selection. In this case, the
+ // selection, but not the current index, reflects the single message
+ // the user right-clicked on.
+ // We want to treat case #1 as if a right-click was not involved and we
+ // want to ignore case #2 by bailing because our existing selection (or
+ // lack thereof) we want maintained.
+// if (gRightMouseButtonDown && gRightMouseButtonChangedSelection)
+// return;
+
+ // save the value, even if it is nsMsgViewIndex_None.
+ this._nextViewIndexAfterDelete = this.view.dbView.msgToSelectAfterDelete;
+ },
+
+ /**
+ * The archive code tells us when it is starting to archive messages. This
+ * is different from hinting about deletion because it will also tell us
+ * when it has completed its mass move.
+ * The UI goal is that we do not immediately jump beyond the selected messages
+ * to the next message until all of the selected messages have been
+ * processed (moved). Ideally we would also do this when deleting messages
+ * from a multiple-folder backed message view, but we don't know when the
+ * last job completes in that case (whereas in this case we do because of the
+ * call to hintMassMoveCompleted.)
+ */
+ hintMassMoveStarting:
+ function FolderDisplayWidget_hintMassMoveStarting() {
+ this.hintAboutToDeleteMessages();
+ this._massMoveActive = true;
+ },
+
+ /**
+ * The archival has completed, we can finally let onMessagseRemoved run to
+ * completion.
+ */
+ hintMassMoveCompleted:
+ function FolderDisplayWidget_hintMassMoveCompleted() {
+ this._massMoveActive = false;
+ this.onMessagesRemoved();
+ },
+
+ /**
+ * When a right-click on the thread pane is going to alter our selection, we
+ * get this notification (currently from |ChangeSelectionWithoutContentLoad|
+ * in msgMail3PaneWindow.js), which lets us save our state.
+ * This ends one of two ways: we get made inactive because a new tab popped up
+ * or we get a call to |hintRightClickSelectionPerturbationDone|.
+ *
+ * Ideally, we could just save off our current nsITreeSelection and restore it
+ * when this is all over. This assumption would rely on the underlying view
+ * not having any changes to its rows before we restore the selection. I am
+ * not confident we can rule out background processes making changes, plus
+ * the right-click itself may mutate the view (although we could try and get
+ * it to restore the selection before it gets to the mutation part). Our
+ * only way to resolve this would be to create a 'tee' like fake selection
+ * that would proxy view change notifications to both sets of selections.
+ * That is hard.
+ * So we just use the existing _saveSelection/_restoreSelection mechanism
+ * which is potentially very costly.
+ */
+ hintRightClickPerturbingSelection:
+ function FolderDisplayWidget_hintRightClickPerturbingSelect() {
+ this._saveSelection();
+ },
+
+ /**
+ * When a right-click on the thread pane altered our selection (which we
+ * should have received a call to |hintRightClickPerturbingSelection| for),
+ * we should receive this notification from
+ * |RestoreSelectionWithoutContentLoad| when it wants to put things back.
+ */
+ hintRightClickSelectionPerturbationDone:
+ function FolderDisplayWidget_hintRightClickSelectionPerturbationDone() {
+ this._restoreSelection();
+ },
+
+ /* ===== End hints from the command infrastructure ==== */
+
+ _updateThreadDisplay: function FolderDisplayWidget__updateThreadDisplay() {
+ if (this.active) {
+ if (this.view.dbView) {
+ this.updateColumns();
+ UpdateSortIndicators(this.view.dbView.sortType,
+ this.view.dbView.sortOrder);
+ SetNewsFolderColumns();
+ }
+ }
+ },
+
+ /**
+ * Update the UI display apart from the thread tree because the folder being
+ * displayed has changed. This can be the result of changing the folder in
+ * this FolderDisplayWidget, or because this FolderDisplayWidget is being
+ * made active. _updateThreadDisplay handles the parts of the thread tree
+ * that need updating.
+ */
+ _updateContextDisplay: function FolderDisplayWidget__updateContextDisplay() {
+ if (this.active) {
+ UpdateMailToolbar("FolderDisplayWidget updating context");
+ UpdateStatusQuota(this.displayedFolder);
+ UpdateStatusMessageCounts(this.displayedFolder);
+
+ // - mail view combo-box.
+ this.onMailViewChanged();
+ }
+ },
+
+ /**
+ * Run the provided notification function right now if we are 'active' (the
+ * currently displayed tab), otherwise queue it to be run when we become
+ * active. We do this because our tabbing model uses multiplexed (reused)
+ * widgets, and extensions likewise depend on these global/singleton things.
+ * If the requested notification function is already queued, it will not be
+ * added a second time, and the original call ordering will be maintained.
+ * If a new call ordering is required, the list of notifications should
+ * probably be reset by the 'big bang' event (new view creation?).
+ */
+ _notifyWhenActive:
+ function FolderDisplayWidget__notifyWhenActive(aNotificationFunc) {
+ if (this._active) {
+ aNotificationFunc.call(this);
+ }
+ else {
+ if (this._notificationsPendingActivation.indexOf(aNotificationFunc) == -1)
+ this._notificationsPendingActivation.push(aNotificationFunc);
+ }
+ },
+
+ /**
+ * Some notifications cannot run while the FolderDisplayWidget is inactive
+ * (presumbly because it is in a background tab). We accumulate those in
+ * _notificationsPendingActivation and then this method runs them when we
+ * become active again.
+ */
+ _runNotificationsPendingActivation:
+ function FolderDisplayWidget__runNotificationsPendingActivation() {
+ if (!this._notificationsPendingActivation.length)
+ return;
+
+ let pendingNotifications = this._notificationsPendingActivation;
+ this._notificationsPendingActivation = [];
+ for each (let [, notif] in Iterator(pendingNotifications)) {
+ notif.call(this);
+ }
+ },
+
+ get active() {
+ return this._active;
+ },
+
+ /**
+ * Make this FolderDisplayWidget the 'active' widget by updating globals and
+ * linking us up to the UI widgets. This is intended for use by the tabbing
+ * logic.
+ */
+ makeActive: function FolderDisplayWidget_makeActive(aWasInactive) {
+ let wasInactive = !this._active;
+
+ // -- globals
+ // update per-tab globals that we own
+ gFolderDisplay = this;
+ gMessageDisplay = this.messageDisplay;
+ gDBView = this.view.dbView;
+ messenger = this.messenger;
+
+ // update singleton globals' state
+ msgWindow.openFolder = this.view.displayedFolder;
+
+ this._active = true;
+ this._runNotificationsPendingActivation();
+
+ // -- UI
+
+ // thread pane if we have a db view
+ if (this.view.dbView) {
+ // Make sure said thread pane is visible. If we do this after we re-root
+ // the tree, the thread pane may not actually replace the account central
+ // pane. Concerning...
+ this._showThreadPane();
+
+ // some things only need to happen if we are transitioning from inactive
+ // to active
+ if (wasInactive) {
+ // Setting the 'view' attribute on treeBox results in the following
+ // effective calls, noting that in makeInactive we made sure to null
+ // out its view so that it won't try and clean up any views or their
+ // selections. (The actual actions happen in nsTreeBodyFrame::SetView)
+ // - this.view.dbView.selection.tree = this.treeBox
+ // - this.view.dbView.setTree(this.treeBox)
+ // - this.treeBox.view = this.view.dbView (in nsTreeBodyObject::SetView)
+ if (this.treeBox) {
+ this.treeBox.view = this.view.dbView;
+ if (this._savedFirstVisibleRow != null)
+ this.treeBox.scrollToRow(this._savedFirstVisibleRow);
+
+ this.restoreColumnStates();
+ }
+
+ // restore the quick search widget
+ let searchInput = document.getElementById("searchInput");
+ if (searchInput && this._savedQuickSearch) {
+ searchInput.searchMode = this._savedQuickSearch.searchMode;
+ if (this._savedQuickSearch.text) {
+ searchInput.value = this._savedQuickSearch.text;
+ searchInput.showingSearchCriteria = false;
+ searchInput.clearButtonHidden = false;
+ }
+ else {
+ searchInput.setSearchCriteriaText();
+ }
+ }
+ }
+
+ // the tab mode knows whether we are folder or message display, which
+ // impacts the legal modes
+ if (this._tabInfo)
+ mailTabType._setPaneStates(this._tabInfo.mode.legalPanes,
+ {folder: !this._tabInfo.folderPaneCollapsed,
+ message: !this._tabInfo.messagePaneCollapsed});
+
+ // update the columns and such that live inside the thread pane
+ this._updateThreadDisplay();
+
+ this.messageDisplay.makeActive();
+ }
+ // account central stuff when we don't have a dbview
+ else {
+ this._showAccountCentral();
+ if (this._tabInfo)
+ mailTabType._setPaneStates(this._tabInfo.mode.legalPanes,
+ {folder: !this._tabInfo.folderPaneCollapsed,
+ message: false});
+ }
+
+ this._updateContextDisplay();
+ },
+
+ /**
+ * Cause the displayDeck to display the thread pane.
+ */
+ _showThreadPane: function FolderDisplayWidget__showThreadPane() {
+ document.getElementById("displayDeck").selectedPanel =
+ document.getElementById("threadPaneBox");
+ },
+
+ /**
+ * Cause the displayDeck to display the (preference configurable) account
+ * central page.
+ */
+ _showAccountCentral: function FolderDisplayWidget__showAccountCentral() {
+ var accountBox = document.getElementById("accountCentralBox");
+ document.getElementById("displayDeck").selectedPanel = accountBox;
+ var prefName = "mailnews.account_central_page.url";
+ // oh yeah, 'pref' is a global all right.
+ var acctCentralPage =
+ pref.getComplexValue(prefName,
+ Components.interfaces.nsIPrefLocalizedString).data;
+ window.frames["accountCentralPane"].location.href = acctCentralPage;
+ },
+
+ /**
+ * Call this when the tab using us is being hidden.
+ */
+ makeInactive: function FolderDisplayWidget_makeInactive() {
+ this._active = false;
+ // save the folder pane's state always
+ this.folderPaneCollapsed =
+ document.getElementById("folderPaneBox").collapsed;
+
+ if (this.view.dbView) {
+ if (this.treeBox)
+ this._savedFirstVisibleRow = this.treeBox.getFirstVisibleRow();
+
+ // save column states
+ this.saveColumnStates();
+
+ // save the message pane's state only when it is potentially visible
+ this.messagePaneCollapsed =
+ document.getElementById("messagepanebox").collapsed;
+
+ // save the actual quick-search query text
+ let searchInput = document.getElementById("searchInput");
+ if (searchInput) {
+ this._savedQuickSearch = {
+ text: searchInput.showingSearchCriteria ? null : searchInput.value,
+ searchMode: searchInput.searchMode
+ };
+ }
+
+ // save off the tree selection object. the nsTreeBodyFrame will make the
+ // view forget about it when our view is removed, so it's up to us to
+ // save it.
+ let treeViewSelection = this.view.dbView.selection;
+ // make the tree forget about the view right now so we can tell the db
+ // view about its selection object so it can try and keep it up-to-date
+ // even while hidden in the background
+ if (this.treeBox)
+ this.treeBox.view = null;
+ // (and tell the db view about its selection again...)
+ this.view.dbView.selection = treeViewSelection;
+
+ // hook the dbview up to the fake tree box
+ this._fakeTreeBox.view = this.view.dbView;
+ this.view.dbView.setTree(this._fakeTreeBox);
+ treeViewSelection.tree = this._fakeTreeBox;
+ }
+
+ this.messageDisplay.makeInactive();
+ },
+
+ /**
+ * @return true if there is a db view and the command is enabled on the view.
+ * This function hides some of the XPCOM-odditities of the getCommandStatus
+ * call.
+ */
+ getCommandStatus: function FolderDisplayWidget_getCommandStatus(
+ aCommandType, aEnabledObj, aCheckStatusObj) {
+ // no view means not enabled
+ if (!this.view.dbView)
+ return false;
+ let enabledObj = {}, checkStatusObj = {};
+ this.view.dbView.getCommandStatus(aCommandType, enabledObj, checkStatusObj);
+ return enabledObj.value;
+ },
+
+ /**
+ * Make code cleaner by allowing peoples to call doCommand on us rather than
+ * having to do folderDisplayWidget.view.dbView.doCommand.
+ *
+ * @param aCommandName The command name to invoke.
+ */
+ doCommand: function FolderDisplayWidget_doCommand(aCommandName) {
+ return this.view.dbView && this.view.dbView.doCommand(aCommandName);
+ },
+
+ /**
+ * Make code cleaner by allowing peoples to call doCommandWithFolder on us
+ * rather than having to do:
+ * folderDisplayWidget.view.dbView.doCommandWithFolder.
+ *
+ * @param aCommandName The command name to invoke.
+ * @param aFolder The folder context for the command.
+ */
+ doCommandWithFolder: function FolderDisplayWidget_doCommandWithFolder(
+ aCommandName, aFolder) {
+ return this.view.dbView &&
+ this.view.dbView.doCommandWithFolder(aCommandName, aFolder);
+ },
+
+
+
+ /**
+ * Navigate using nsMsgNavigationType rules and ensuring the resulting row is
+ * visible. This is trickier than it used to be because we now support
+ * treating collapsed threads as the set of all the messages in the collapsed
+ * thread rather than just the root message in that thread.
+ *
+ * @param aNavType {nsMsgNavigationType} navigation command.
+ * @param aSelect {Boolean} should we select the message if we find one?
+ * Defaults to true if omitted.
+ *
+ * @return true if the navigation constraint matched anything, false if not.
+ * We will have navigated if true, we will have done nothing if false.
+ */
+ navigate: function FolderDisplayWidget_navigate(aNavType, aSelect) {
+ if (aSelect === undefined)
+ aSelect = true;
+ let resultKeyObj = {}, resultIndexObj = {}, threadIndexObj = {};
+
+ let summarizeSelection =
+ gPrefBranch.getBoolPref("mail.operate_on_msgs_in_collapsed_threads");
+
+ let treeSelection = this.treeSelection; // potentially magic getter
+ let currentIndex = treeSelection ? treeSelection.currentIndex : 0;
+
+ let viewIndex;
+ // if we're doing next unread, and a collapsed thread is selected, and
+ // the top level message is unread, just set the result manually to
+ // the top level message, without using viewNavigate.
+ if (summarizeSelection &&
+ aNavType == nsMsgNavigationType.nextUnreadMessage &&
+ currentIndex != -1 &&
+ this.view.isCollapsedThreadAtIndex(currentIndex) &&
+ !(this.view.dbView.getFlagsAt(currentIndex) &
+ nsMsgMessageFlags.Read)) {
+ viewIndex = currentIndex;
+ }
+ else {
+ // always 'wrap' because the start index is relative to the selection.
+ // (keep in mind that many forms of navigation do not care about the
+ // starting position or 'wrap' at all; for example, firstNew just finds
+ // the first new message.)
+ // allegedly this does tree-expansion for us.
+ this.view.dbView.viewNavigate(aNavType, resultKeyObj, resultIndexObj,
+ threadIndexObj, true);
+ viewIndex = resultIndexObj.value;
+ }
+
+ if (viewIndex == nsMsgViewIndex_None)
+ return false;
+
+ // - Expand if required.
+ // (The nsMsgDBView isn't really aware of the varying semantics of
+ // collapsed threads, so viewNavigate might tell us about the root message
+ // and leave it collapsed, not realizing that it needs to be expanded.)
+ if (summarizeSelection &&
+ this.view.isCollapsedThreadAtIndex(viewIndex))
+ this.view.dbView.toggleOpenState(viewIndex);
+
+ if (aSelect)
+ this.selectViewIndex(viewIndex);
+ else
+ this.ensureRowIsVisible(viewIndex);
+ return true;
+ },
+
+ /**
+ * Push a call to |navigate| to be what we do once we successfully open the
+ * next folder. This is intended to be used by cross-folder navigation
+ * code. It should call this method before triggering the folder change.
+ */
+ pushNavigation: function FolderDisplayWidget_navigate(aNavType, aSelect) {
+ this._pendingNavigation = [aNavType, aSelect];
+ },
+
+ /**
+ * @return true if we are able to navigate using the given navigation type at
+ * this time.
+ */
+ navigateStatus: function FolderDisplayWidget_navigateStatus(aNavType) {
+ if (!this.view.dbView)
+ return false;
+ return this.view.dbView.navigateStatus(aNavType);
+ },
+
+ /**
+ * @returns the message header for the first selected message, or null if
+ * there is no selected message.
+ *
+ * If the user has right-clicked on a message, this method will return that
+ * message and not the 'current index' (the dude with the dotted selection
+ * rectangle around him.) If you instead always want the currently
+ * displayed message (which is not impacted by right-clicking), then you
+ * would want to access the displayedMessage property on the
+ * MessageDisplayWidget. You can get to that via the messageDisplay
+ * attribute on this object or (potentially) via the gMessageDisplay object.
+ */
+ get selectedMessage FolderDisplayWidget_get_selectedMessage() {
+ // there are inconsistencies in hdrForFirstSelectedMessage between
+ // nsMsgDBView and nsMsgSearchDBView in whether they use currentIndex,
+ // do it ourselves. (nsMsgDBView does not use currentIndex, search does.)
+ let treeSelection = this.treeSelection;
+ if (!treeSelection || !treeSelection.count)
+ return null;
+ let minObj = {}, maxObj = {};
+ treeSelection.getRangeAt(0, minObj, maxObj);
+ return this.view.dbView.getMsgHdrAt(minObj.value);
+ },
+
+ /**
+ * @return true if there is a selected message and it's an RSS feed message.
+ */
+ get selectedMessageIsFeed FolderDisplayWidget_get_selectedMessageIsFeed() {
+ let message = this.selectedMessage;
+ return Boolean(message && message.folder &&
+ message.folder.server.type == 'rss');
+ },
+
+ /**
+ * @return true if there is a selected message and it's an IMAP message.
+ */
+ get selectedMessageIsImap FolderDisplayWidget_get_selectedMessageIsImap() {
+ let message = this.selectedMessage;
+ return Boolean(message && message.folder &&
+ message.folder.flags & nsMsgFolderFlags.ImapBox);
+ },
+
+ /**
+ * @return true if there is a selected message and it's a news message. It
+ * would be great if messages knew this about themselves, but they don't.
+ */
+ get selectedMessageIsNews FolderDisplayWidget_get_selectedMessageIsNews() {
+ let message = this.selectedMessage;
+ return Boolean(message && message.folder &&
+ (message.folder.flags & nsMsgFolderFlags.Newsgroup));
+ },
+
+ /**
+ * @return true if there is a selected message and it's an external message,
+ * meaning it is loaded from an .eml file on disk or is an rfc822 attachment
+ * on a message.
+ */
+ get selectedMessageIsExternal
+ FolderDisplayWidget_get_selectedMessageIsExternal() {
+ let message = this.selectedMessage;
+ // Dummy messages currently lack a folder. This is not a great heuristic.
+ // I have annotated msgHdrViewOverlay.js which provides the dummy header to
+ // express this implementation dependency.
+ // (Currently, since external mails can only be opened in standalone windows
+ // which subclass us, we could always return false, and have the subclass
+ // return true using its own heuristics. But since we are moving to a tab
+ // model more heavily, at some point the 3-pane will need this.)
+ return Boolean(message && !message.folder);
+ },
+
+ /**
+ * @return the number of selected messages. If the
+ * "mail.operate_on_msgs_in_collapsed_threads" preference is enabled, then
+ * any collapsed thread roots that are selected will also conceptually have
+ * all of the messages in that thread selected.
+ */
+ get selectedCount FolderDisplayWidget_get_selectedCount() {
+ if (!this.view.dbView)
+ return 0;
+ return this.view.dbView.numSelected;
+ },
+
+ /**
+ * Provides a list of the view indices that are selected which is *not* the
+ * same as the rows of the selected messages. When the
+ * "mail.operate_on_msgs_in_collapsed_threads" preference is enabled,
+ * messages may be selected but not visible (because the thread root is
+ * selected.)
+ * You probably want to use the |selectedMessages| attribute instead of this
+ * one. (Or selectedMessageUris in some rare cases.)
+ *
+ * If the user has right-clicked on a message, this will return that message
+ * and not the selection prior to the right-click.
+ *
+ * @return a list of the view indices that are currently selected
+ */
+ get selectedIndices FolderDisplayWidget_get_selectedIndices() {
+ if (!this.view.dbView)
+ return [];
+
+ return this.view.dbView.getIndicesForSelection({});
+ },
+
+ /**
+ * Provides a list of the message headers for the currently selected messages.
+ * If the "mail.operate_on_msgs_in_collapsed_threads" preference is enabled,
+ * then any collapsed thread roots that are selected will also (conceptually)
+ * have all of the messages in that thread selected and they will be included
+ * in the returned list.
+ *
+ * If the user has right-clicked on a message, this will return that message
+ * (and any collapsed children if so enabled) and not the selection prior to
+ * the right-click.
+ *
+ * @return a list of the message headers for the currently selected messages.
+ * If there are no selected messages, the result is an empty list.
+ */
+ get selectedMessages FolderDisplayWidget_get_selectedMessages() {
+ if (!this.view.dbView)
+ return [];
+ // getMsgHdrsForSelection returns an nsIMutableArray. We want our callers
+ // to have a user-friendly JS array and not have to worry about
+ // QueryInterfacing the values (or needing to know to use fixIterator).
+ return [msgHdr for each
+ (msgHdr in fixIterator(
+ this.view.dbView.getMsgHdrsForSelection().enumerate(),
+ Components.interfaces.nsIMsgDBHdr))];
+ },
+
+ /**
+ * @return a list of the URIs for the currently selected messages or null
+ * (instead of a list) if there are no selected messages. Do not
+ * pass around URIs unless you have a good reason. Legacy code is an
+ * ok reason.
+ *
+ * If the user has right-clicked on a message, this will return that message's
+ * URI and not the selection prior to the right-click.
+ */
+ get selectedMessageUris FolderDisplayWidget_get_selectedMessageUris() {
+ if (!this.view.dbView)
+ return null;
+
+ let messageArray = this.view.dbView.getURIsForSelection({});
+ return messageArray.length ? messageArray : null;
+ },
+
+ /**
+ * Clear the tree selection, making sure the message pane is cleared and
+ * the context display (toolbars, etc.) are updated.
+ */
+ clearSelection: function FolderDisplayWidget_clearSelection() {
+ let treeSelection = this.treeSelection; // potentially magic getter
+ if (!treeSelection)
+ return;
+ treeSelection.clearSelection();
+ this.messageDisplay.clearDisplay();
+ this._updateContextDisplay();
+ },
+
+ /**
+ * Select a message for display by header. If the view is active, attempt
+ * to select the message right now. If the view is not active or we were
+ * unable to find it, update our saved selection to want to display the
+ * message. Threads are expanded to find the header.
+ *
+ * @param aMsgHdr The message header to select for display.
+ */
+ selectMessage: function FolderDisplayWidget_selectMessage(aMsgHdr,
+ aForceNotification) {
+ if (this.active) {
+ let viewIndex = this.view.dbView.findIndexOfMsgHdr(aMsgHdr, true);
+ if (viewIndex != nsMsgViewIndex_None) {
+ this.selectViewIndex(viewIndex);
+ return;
+ }
+ }
+ this._savedSelection = [{messageId: aMsgHdr.messageId}];
+ // queue the selection to be restored once we become active if we are not
+ // active.
+ if (!this.active)
+ this._notifyWhenActive(this._restoreSelection);
+ },
+
+ /**
+ * Select all of the provided nsIMsgDBHdrs in the aMessages array, expanding
+ * threads as required. If the view is not active, or we were not able to
+ * find all of the messages, update our saved selection to want to display
+ * the messages. The messages will then be selected when we are made active
+ * or all messages in the folder complete loading. This is to accomodate the
+ * use-case where we are backed by an in-progress search and no
+ *
+ * @param aMessages An array of nsIMsgDBHdr instances.
+ * @param aDoNotNeedToFindAll If true (can be omitted and left undefined), we
+ * do not attempt to save the selection for future use. This is intended
+ * for use by the _restoreSelection call which is the end-of-the-line for
+ * restoring the selection. (Once it gets called all of our messages
+ * should have already been loaded.)
+ */
+ selectMessages: function FolderDisplayWidget_selectMessages(
+ aMessages, aDoNotNeedToFindAll) {
+ if (this.active && this.treeSelection) {
+ let foundAll = true;
+ let treeSelection = this.treeSelection;
+ let dbView = this.view.dbView;
+ let minRow = null, maxRow = null;
+
+ treeSelection.selectEventsSuppressed = true;
+ treeSelection.clearSelection();
+
+ for each (let [, msgHdr] in Iterator(aMessages)) {
+ let viewIndex = dbView.findIndexOfMsgHdr(msgHdr, true);
+ if (viewIndex != nsMsgViewIndex_None) {
+ if (minRow == null || viewIndex < minRow)
+ minRow = viewIndex;
+ if (maxRow == null || viewIndex > maxRow )
+ maxRow = viewIndex;
+ // nsTreeSelection is actually very clever about doing this
+ // efficiently.
+ treeSelection.rangedSelect(viewIndex, viewIndex, true);
+ }
+ else {
+ foundAll = false;
+ }
+
+ // make sure the selection is as visible as possible
+ if (minRow != null)
+ this.ensureRowRangeIsVisible(minRow, maxRow);
+ }
+
+ treeSelection.selectEventsSuppressed = false;
+
+ if (!aDoNotNeedToFindAll || foundAll)
+ return;
+ }
+ this._savedSelection = [{messageId: msgHdr.messageId} for each
+ ([, msgHdr] in Iterator(aMessages))];
+ if (!this.active)
+ this._notifyWhenActive(this._restoreSelection);
+ },
+
+ /**
+ * Select the message at view index.
+ *
+ * @param aViewIndex The view index to select. This will be bounds-checked
+ * and if it is outside the bounds, we will clear the selection and
+ * bail.
+ */
+ selectViewIndex: function FolderDisplayWidget_selectViewIndex(aViewIndex) {
+ let treeSelection = this.treeSelection;
+ // if we have no selection, we can't select something
+ if (!treeSelection)
+ return;
+ let rowCount = this.view.dbView.rowCount;
+ if ((aViewIndex == nsMsgViewIndex_None) ||
+ (aViewIndex < 0) || (aViewIndex >= rowCount)) {
+ this.clearSelection();
+ return;
+ }
+
+ // Check whether the index is already selected/current. This can be the
+ // case when we are here as the result of a deletion. Assuming
+ // nsMsgDBView::NoteChange ran and was not suppressing change
+ // notifications, then it's very possible the selection is already where
+ // we want it to go. However, in that case, nsMsgDBView::SelectionChanged
+ // bailed without doing anything because m_deletingRows...
+ // So we want to generate a change notification if that is the case. (And
+ // we still want to call ensureRowIsVisible, as there may be padding
+ // required.)
+ if ((treeSelection.count == 1) &&
+ ((treeSelection.currentIndex == aViewIndex) ||
+ treeSelection.isSelected(aViewIndex))) {
+ // Make sure the index we just selected is also the current index.
+ // This can happen when the tree selection adjusts itself as a result of
+ // changes to the tree as a result of deletion. This will not trigger
+ // a notification.
+ treeSelection.select(aViewIndex);
+ this.view.dbView.selectionChanged();
+ }
+ // Previous code was concerned about avoiding updating commands on the
+ // assumption that only the selection count mattered. We no longer
+ // make this assumption.
+ // Things that may surprise you about the call to treeSelection.select:
+ // 1) This ends up calling the onselect method defined on the XUL 'tree'
+ // tag. For the 3pane this is the ThreadPaneSelectionChanged method in
+ // threadPane.js. That code checks a global to see if it is dealing
+ // with a right-click, and ignores it if so.
+ else {
+ treeSelection.select(aViewIndex);
+ }
+
+ this.ensureRowIsVisible(aViewIndex);
+ },
+
+ /**
+ * For every selected message in the display that is part of a (displayed)
+ * thread and is not the root message, de-select it and ensure that the
+ * root message of the thread is selected.
+ * This is primarily intended to be used when collapsing visible threads.
+ *
+ * We do nothing if we are not in a threaded display mode.
+ */
+ selectSelectedThreadRoots:
+ function FolderDisplayWidget_selectSelectedThreadRoots() {
+ if (!this.view.showThreaded)
+ return;
+
+ // There are basically two implementation strategies available to us:
+ // 1) For each selected view index with a level > 0, keep walking 'up'
+ // (numerically smaller) until we find a message with level 0.
+ // The inefficiency here is the potentially large number of JS calls
+ // into XPCOM space that will be required.
+ // 2) Ask for the thread that each view index belongs to, use that to
+ // efficiently retrieve the thread root, then find the root using
+ // the message header. The inefficiency here is that the view
+ // currently does a linear scan, albeit a relatively efficient one.
+ // And the winner is... option 2, because the code is simpler because we
+ // can reuse selectMessages to do most of the work.
+ let selectedIndices = this.selectedIndices;
+ let newSelectedMessages = [];
+ let dbView = this.view.dbView;
+ for each (let [, index] in Iterator(selectedIndices)) {
+ let thread = dbView.getThreadContainingIndex(index);
+ // We use getChildHdrAt instead of getRootHdr because getRootHdr has
+ // a useless out-param and just calls getChildHdrAt anyways.
+ newSelectedMessages.push(thread.getChildHdrAt(0));
+ }
+ this.selectMessages(newSelectedMessages);
+ },
+
+ /**
+ * Number of padding messages before the 'focused' message when it is at the
+ * top of the thread pane.
+ */
+ TOP_VIEW_PADDING: 1,
+ /**
+ * Number of padding messages after the 'focused' message when it is at the
+ * bottom of the thread pane and lip padding does not apply.
+ */
+ BOTTOM_VIEW_PADDING: 1,
+
+ /**
+ * Ensure the given view index is visible, preferably with some padding.
+ * By padding, we mean that the index will not be the first or last message
+ * displayed, but rather have messages on either side.
+ * If we get near the end of the list of messages, we 'snap' to the last page
+ * of messages. The intent is that we later implement a
+ * We have the concept of a 'lip' when we are at the end of the message
+ * display. If we are near the end of the display, we want to show an
+ * empty row (at the bottom) so the user knows they are at the end. Also,
+ * if a message shows up that is new and things are sorted ascending, this
+ * turns out to be useful.
+ */
+ ensureRowIsVisible: function FolderDisplayWidget_ensureRowIsVisible(
+ aViewIndex, aBounced) {
+ // Dealing with the tree view layout is a nightmare, let's just always make
+ // sure we re-schedule ourselves. The most particular rationale here is
+ // that the message pane may be toggling its state and it's much simpler
+ // and reliable if we ensure that all of FolderDisplayWidget's state
+ // change logic gets to run to completion before we run ourselves.
+ if (!aBounced) {
+ let dis = this;
+ window.setTimeout(function() {
+ dis.ensureRowIsVisible(aViewIndex, true);
+ }, 0);
+ }
+
+ let treeBox = this.treeBox;
+ if (!treeBox)
+ return;
+
+ // try and trigger a reflow...
+ treeBox.height;
+
+ let maxIndex = this.view.dbView.rowCount - 1;
+
+ let first = treeBox.getFirstVisibleRow();
+ // Assume the bottom row is half-visible and should generally be ignored.
+ // (We could actually do the legwork to see if there is a partial one...)
+ const halfVisible = 1;
+ let last = treeBox.getLastVisibleRow() - halfVisible;
+ let span = treeBox.getPageLength() - halfVisible;
+
+ let target;
+ // If the index is near the end, try and latch on to the bottom.
+ if (aViewIndex + span - this.TOP_VIEW_PADDING > maxIndex)
+ target = maxIndex - span;
+ // If the index is after the last visible guy (with padding), move down
+ // so that the target index is padded in 1 from the bottom.
+ else if (aViewIndex >= last - this.BOTTOM_VIEW_PADDING)
+ target = Math.min(maxIndex, aViewIndex + this.BOTTOM_VIEW_PADDING) -
+ span;
+ // If the index is before the first visible guy (with padding), move up
+ else if (aViewIndex <= first + this.TOP_VIEW_PADDING) // move up
+ target = Math.max(0, aViewIndex - this.TOP_VIEW_PADDING);
+ else // it is already visible
+ return;
+
+ // this sets the first visible row
+ treeBox.scrollToRow(target);
+ },
+
+ /**
+ * Ensure that the given range of rows is visible maximally visible in the
+ * thread pane. If the range is larger than the number of rows that can be
+ * displayed in the thread pane, we bias towards showing the min row (with
+ * padding).
+ *
+ * @param aMinRow The numerically smallest row index defining the start of
+ * the inclusive range.
+ * @param aMaxRow The numberically largest row index defining the end of the
+ * inclusive range.
+ */
+ ensureRowRangeIsVisible:
+ function FolderDisplayWidget_ensureRowRangeIsVisible(aMinRow, aMaxRow,
+ aBounced) {
+ // Dealing with the tree view layout is a nightmare, let's just always make
+ // sure we re-schedule ourselves. The most particular rationale here is
+ // that the message pane may be toggling its state and it's much simpler
+ // and reliable if we ensure that all of FolderDisplayWidget's state
+ // change logic gets to run to completion before we run ourselves.
+ if (!aBounced) {
+ let dis = this;
+ window.setTimeout(function() {
+ dis.ensureRowRangeIsVisible(aMinRow, aMaxRow, true);
+ }, 0);
+ }
+
+ let treeBox = this.treeBox;
+ if (!treeBox)
+ return;
+ let first = treeBox.getFirstVisibleRow();
+ const halfVisible = 1;
+ let last = treeBox.getLastVisibleRow() - halfVisible;
+ let span = treeBox.getPageLength() - halfVisible;
+
+ // bail if the range is already visible with padding constraints handled
+ if ((first + this.TOP_VIEW_PADDING <= aMinRow) &&
+ (last - this.BOTTOM_VIEW_PADDING >= aMaxRow))
+ return;
+
+ let target;
+ // if the range is bigger than we can fit, optimize position for the min row
+ // with padding to make it obvious the range doesn't extend above the row.
+ if (aMaxRow - aMinRow > span)
+ target = Math.max(0, aMinRow - this.TOP_VIEW_PADDING);
+ // So the range must fit, and it's a question of how we want to position it.
+ // For now, the answer is we try and center it, why not.
+ else {
+ let rowSpan = aMaxRow - aMinRow + 1;
+ let halfSpare = parseInt((span - rowSpan - this.TOP_VIEW_PADDING -
+ this.BOTTOM_VIEW_PADDING) / 2);
+ target = aMinRow - halfSpare - this.TOP_VIEW_PADDING;
+ }
+ treeBox.scrollToRow(target);
+ },
+
+ /**
+ * Ensure that the selection is visible to the extent possible.
+ */
+ ensureSelectionIsVisible:
+ function FolderDisplayWidget_ensureSelectionIsVisible() {
+ let treeSelection = this.treeSelection; // potentially magic getter
+ if (!treeSelection || !treeSelection.count)
+ return;
+
+ let minRow = null, maxRow = null;
+
+ let rangeCount = treeSelection.getRangeCount();
+ for (let iRange = 0; iRange < rangeCount; iRange++) {
+ let rangeMinObj = {}, rangeMaxObj = {};
+ treeSelection.getRangeAt(iRange, rangeMinObj, rangeMaxObj);
+ let rangeMin = rangeMinObj.value, rangeMax = rangeMaxObj.value;
+ if (minRow == null || rangeMin < minRow)
+ minRow = rangeMin;
+ if (maxRow == null || rangeMax > maxRow )
+ maxRow = rangeMax;
+ }
+
+ this.ensureRowRangeIsVisible(minRow, maxRow);
+ }
+};
+
+/**
+ * Implement a fake nsITreeBoxObject so that we can keep the view
+ * nsITreeSelection selections 'live' when they are in the background. We need
+ * to do this because nsTreeSelection changes its behaviour (and gets ornery)
+ * if it does not have a box object.
+ * This does not need to exist once we abandon multiplexed tabbing.
+ *
+ * Sometimes, nsTreeSelection tries to turn us into an nsIBoxObject and then in
+ * turn get the associated element, and then create DOM events on that. We
+ * can't really stop that, but we can use misdirection to tell it about a box
+ * object that we don't care about. That way it gets the bogus events,
+ * effectively blackholing them.
+ */
+function FakeTreeBoxObject(aDummyBoxObject) {
+ this.dummyBoxObject = aDummyBoxObject.QueryInterface(
+ Components.interfaces.nsIBoxObject);
+ this.view = null;
+}
+FakeTreeBoxObject.prototype = {
+ view: null,
+ /**
+ * No need to actually invalidate, as when we re-root the view this will
+ * happen.
+ */
+ invalidate: function FakeTreeBoxObject_invalidate() {
+ // NOP
+ },
+ invalidateRange: function FakeTreeBoxObject_invalidateRange() {
+ // NOP
+ },
+ invalidateRow: function FakeTreeBoxObject_invalidateRow() {
+ // NOP
+ },
+ beginUpdateBatch: function FakeTreeBoxObject_beginUpdateBatch() {
+
+ },
+ endUpdateBatch: function FakeTreeBoxObject_endUpdateBatch() {
+
+ },
+ rowCountChanged: function FakeTreeBoxObject_rowCountChanged() {
+
+ },
+ /**
+ * Sleight of hand! If someone asks us about an nsIBoxObject, we tell them
+ * about a real box object that is just a dummy and is never used for
+ * anything.
+ */
+ QueryInterface: function FakeTreeBoxObject_QueryInterface(aIID) {
+ if (aIID.equals(Components.interfaces.nsIBoxObject))
+ return this.dummyBoxObject;
+ if (!aIID.equals(Components.interfaces.nsISupports) &&
+ !aIID.equals(Components.interfaces.nsITreeBoxObject))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+};
+/*
+ * Provide attribute and function implementations that complain very loudly if
+ * they are used. Now, XPConnect will return an error to callers if we don't
+ * implement part of the interface signature, but this is unlikely to provide
+ * the visibility we desire. In fact, since it is a simple nsresult error,
+ * it may make things completely crazy. So this way we can yell via dump,
+ * throw an exception, etc.
+ */
+function FTBO_stubOutAttributes(aObj, aAttribNames) {
+ for (let [, attrName] in Iterator(aAttribNames)) {
+ let myAttrName = attrName;
+ aObj.__defineGetter__(attrName,
+ function() {
+ let msg = "Read access to stubbed attribute " + myAttrName;
+ dump(msg + "\n");
+ debugger;
+ throw new Error(msg);
+ });
+ aObj.__defineSetter__(attrName,
+ function() {
+ let msg = "Write access to stubbed attribute " + myAttrName;
+ dump(msg + "\n");
+ debugger;
+ throw new Error(msg);
+ });
+ }
+}
+function FTBO_stubOutMethods(aObj, aMethodNames) {
+ for (let [, methodName] in Iterator(aMethodNames)) {
+ let myMethodName = methodName;
+ aObj[myMethodName] = function() {
+ let msg = "Call to stubbed method " + myMethodName;
+ dump(msg + "\n");
+ debugger;
+ throw new Error(msg);
+ };
+ }
+}
+FTBO_stubOutAttributes(FakeTreeBoxObject.prototype, [
+ "columns",
+ "focused",
+ "treeBody",
+ "rowHeight",
+ "rowWidth",
+ "horizontalPosition",
+ "selectionRegion",
+ ]);
+FTBO_stubOutMethods(FakeTreeBoxObject.prototype, [
+ "getFirstVisibleRow",
+ "getLastVisibleRow",
+ "getPageLength",
+ "ensureRowIsVisible",
+ "ensureCellIsVisible",
+ "scrollToRow",
+ "scrollByLines",
+ "scrollByPages",
+ "scrollToCell",
+ "scrollToColumn",
+ "scrollToHorizontalPosition",
+ "invalidateColumn",
+ "invalidateRow",
+ "invalidateCell",
+ "invalidateColumnRange",
+ "getRowAt",
+ "getCellAt",
+ "getCoordsForCellItem",
+ "isCellCropped",
+ "clearStyleAndImageCaches",
+ ]);
\ No newline at end of file
--- a/mail/base/content/folderPane.js
+++ b/mail/base/content/folderPane.js
@@ -58,17 +58,17 @@ let gFolderTreeView = {
load: function ftv_load(aTree, aJSONFile) {
const Cc = Components.classes;
const Ci = Components.interfaces;
this._treeElement = aTree;
let smartName = document.getElementById("bundle_messenger")
.getString("folderPaneHeader_smart");
this.registerMode("smart", this._smartFoldersGenerator, smartName);
- // the folder pane can be used for other trees which may not have these elements.
+ // the folder pane can be used for other trees which may not have these elements.
if (document.getElementById("folderpane_splitter"))
document.getElementById("folderpane_splitter").collapsed = false;
if (document.getElementById("folderPaneBox"))
document.getElementById("folderPaneBox").collapsed = false;
try {
// Normally our tree takes care of keeping the last selected by itself.
// However older versions of TB stored this in a preference, which we need
@@ -105,17 +105,17 @@ let gFolderTreeView = {
.createInstance(Ci.nsIFileInputStream);
let sstream = Cc["@mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
fstream.init(file, -1, 0, 0);
sstream.init(fstream);
while (sstream.available())
data += sstream.read(4096);
-
+
sstream.close();
fstream.close();
let JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
this._persistOpenMap = JSON.decode(data);
}
}
// Load our data
@@ -211,17 +211,17 @@ let gFolderTreeView = {
aEvent.button != 0 || aEvent.originalTarget.localName == "twisty" ||
aEvent.originalTarget.localName == "slider" ||
aEvent.originalTarget.localName == "scrollbarbutton")
return;
let row = gFolderTreeView._treeElement.treeBoxObject.getRowAt(aEvent.clientX,
aEvent.clientY);
let folderItem = gFolderTreeView._rowMap[row];
- if (folderItem)
+ if (folderItem)
folderItem.command();
// Don't let the double-click toggle the open state of the folder here
aEvent.stopPropagation();
},
getFolderAtCoords: function ftv_getFolderAtCoords(aX, aY) {
let row = gFolderTreeView._treeElement.treeBoxObject.getRowAt(aX, aY);
@@ -265,17 +265,17 @@ let gFolderTreeView = {
* that the folder is actually being displayed (that is, that none of its
* ancestors are collapsed.
*
* @param aFolder the nsIMsgFolder to select
*/
selectFolder: function ftv_selectFolder(aFolder) {
// "this" inside the nested function refers to the function...
// Also note that openIfNot is recursive.
- let tree = this;
+ let tree = this;
function openIfNot(aFolderToOpen) {
let index = tree.getIndexOfFolder(aFolderToOpen);
if (index) {
if (!tree._rowMap[index].open)
tree._toggleRow(index, false);
return;
}
@@ -420,16 +420,31 @@ let gFolderTreeView = {
return false;
}
return true;
}
// allow subscribing to feeds by dragging an url to a feed account
else if (Array.indexOf(types, "text/x-moz-url") != -1 &&
targetFolder.server.type == "rss")
return true;
+ else if (Array.indexOf(types, "application/x-moz-file") != -1) {
+ if (aOrientation != Ci.nsITreeView.DROP_ON)
+ return false;
+ // Don't allow drop onto server itself.
+ if (targetFolder.isServer)
+ return false;
+ // Don't allow drop into a folder that cannot take messages.
+ if (!targetFolder.canFileMessages)
+ return false;
+ for (let i = 0; i < dt.mozItemCount; i++) {
+ let extFile = dt.mozGetDataAt("application/x-moz-file", i)
+ .QueryInterface(Ci.nsILocalFile);
+ return extFile.isFile();
+ }
+ }
return false;
},
drop: function ftv_drop(aRow, aOrientation) {
const Cc = Components.classes;
const Ci = Components.interfaces;
let targetFolder = gFolderTreeView._rowMap[aRow]._folder;
let dt = this._currentTransfer;
@@ -444,17 +459,17 @@ let gFolderTreeView = {
let folders = new Array;
folders.push(dt.mozGetDataAt("text/x-moz-folder", i)
.QueryInterface(Ci.nsIMsgFolder));
let array = toXPCOMArray(folders, Ci.nsIMutableArray);
cs.CopyFolders(array, targetFolder,
(folders[0].server == targetFolder.server), null,
msgWindow);
}
- }
+ }
else if (Array.indexOf(types, "text/x-moz-newsfolder") != -1) {
// Start by getting folders into order.
let folders = new Array;
for (let i = 0; i < count; i++) {
let folder = dt.mozGetDataAt("text/x-moz-newsfolder", i)
.QueryInterface(Ci.nsIMsgFolder);
folders[this.getIndexOfFolder(folder)] = folder;
}
@@ -491,16 +506,27 @@ let gFolderTreeView = {
prefBranch.setCharPref("last_msg_movecopy_target_uri", targetFolder.URI);
prefBranch.setBoolPref("last_msg_movecopy_was_move", isMove);
// ### ugh, so this won't work with cross-folder views. We would
// really need to partition the messages by folder.
cs.CopyMessages(sourceFolder, array, targetFolder, isMove, null,
msgWindow, true);
}
+ else if (Array.indexOf(types, "application/x-moz-file") != -1) {
+ for (let i = 0; i < count; i++) {
+ let extFile = dt.mozGetDataAt("application/x-moz-file", i)
+ .QueryInterface(Ci.nsILocalFile);
+ if (extFile.isFile()) {
+ let len = extFile.leafName.length;
+ if (len > 4 && extFile.leafName.substr(len - 4).toLowerCase() == ".eml")
+ cs.CopyFileMessage(extFile, targetFolder, null, false, 1, "", null, msgWindow);
+ }
+ }
+ }
else if (Array.indexOf(types, "text/x-moz-url") != -1) {
// This is a potential rss feed to subscribe to
// and there's only one, so just get the 0th element.
let url = dt.mozGetDataAt("text/x-moz-url", 0);
let uri = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService).newURI(url, null, null);
if (!(uri.schemeIs("http") || uri.schemeIs("https")) ||
targetFolder.server.type != "rss")
@@ -1027,17 +1053,17 @@ let gFolderTreeView = {
acct.incomingServer.rootFolder.subFolders;
}
catch (ex) {
Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService)
.logStringMessage("Discovering folders for account failed with exception: " + ex);
}
}
-
+
return [new ftvItem(acct.incomingServer.rootFolder)
for each (acct in accounts)];
},
/**
* The unread mode returns all folders that are not root-folders and that
* have unread items. Also always keep the currently selected folder
* so it doesn't disappear under the user.
@@ -1129,17 +1155,17 @@ let gFolderTreeView = {
for each (let folder in ftv._enumerateFolders)
addIfRecent(folder);
recentFolders.sort(sorter);
let items = [new ftvItem(f) for each (f in recentFolders)];
- // There are no children in this view!
+ // There are no children in this view!
// And we want to display the account name to distinguish folders w/
// the same name.
for each (let folder in items) {
folder.__defineGetter__("children", function() []);
folder.addServerName = true;
}
return items;
@@ -1253,17 +1279,17 @@ let gFolderTreeView = {
OnItemRemoved: function ftl_remove(aRDFParentItem, aItem) {
if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
return;
let persistMapIndex = this._persistOpenMap[this.mode].indexOf(aItem.URI);
if (persistMapIndex != -1)
this._persistOpenMap[this.mode].splice(persistMapIndex, 1);
-
+
let index = this.getIndexOfFolder(aItem);
if (!index)
return;
// forget our parent's children; they'll get rebuilt
if (aRDFParentItem)
this._rowMap[index]._parent._children = null;
let kidCount = 1;
let walker = Number(index) + 1;
@@ -1465,23 +1491,16 @@ let gFolderTreeController = {
msgDB.summaryValid = false;
try {
folder.closeAndBackupFolderDB("");
}
catch(e) {
// In a failure, proceed anyway since we're dealing with problems
folder.ForceDBClosed();
}
- // these lines will cause the thread pane to get reloaded when the
- // download/reparse is finished. Only do this if the selected folder is
- // loaded (i.e., not thru the context menu on a non-loaded folder).
- if (folder == GetLoadedMsgFolder()) {
- gRerootOnFolderLoad = true;
- gCurrentFolderToReroot = folder.URI;
- }
folder.updateFolder(msgWindow);
}
window.openDialog("chrome://messenger/content/folderProps.xul", "",
"chrome,centerscreen,titlebar,modal",
{folder: folder, serverType: folder.server.type,
msgWindow: msgWindow, title: title,
okCallback: editFolderCallback,
@@ -1500,17 +1519,16 @@ let gFolderTreeController = {
let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
//xxx no need for uri now
let controller = this;
function renameCallback(aName, aUri) {
if (aUri != folder.URI)
Components.utils.reportError("got back a different folder to rename!");
- controller._resetThreadPane();
controller._tree.view.selection.clearSelection();
// Actually do the rename
folder.rename(aName, msgWindow);
}
window.openDialog("chrome://messenger/content/renameFolderDialog.xul",
"newFolder", "chrome,titlebar,modal",
{preselectedURI: folder.URI,
@@ -1559,21 +1577,19 @@ let gFolderTreeController = {
}
if (folder.flags & FLAGS.Virtual) {
let confirmation = bundle.getString("confirmSavedSearchDeleteMessage");
let title = bundle.getString("confirmSavedSearchTitle");
let IPS = Components.interfaces.nsIPromptService;
if (Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(IPS)
- .confirmEx(window, title, confirmation, IPS.STD_YES_NO_BUTTONS + IPS.BUTTON_POS_1_DEFAULT,
+ .confirmEx(window, title, confirmation, IPS.STD_YES_NO_BUTTONS + IPS.BUTTON_POS_1_DEFAULT,
"", "", "", "", {}) != 0) /* the yes button is in position 0 */
return;
- if (gCurrentVirtualFolderUri == folder.URI)
- gCurrentVirtualFolderUri = null;
}
let array = toXPCOMArray([folder], Ci.nsIMutableArray);
folder.parent.deleteSubFolders(array, msgWindow);
},
/**
* Prompts the user to confirm and empties the trash for the selected folder
@@ -1621,20 +1637,16 @@ let gFolderTreeController = {
*/
compactFolders: function ftc_compactFolders(aFolders) {
let folders = aFolders || gFolderTreeView.getSelectedFolders();
for (let i = 0; i < folders.length; i++) {
// Can't compact folders that have just been compacted.
if (folders[i].server.type != "imap" && !folders[i].expungedBytes)
continue;
- // Reset thread pane for non-imap folders if the folder is selected.
- if (gDBView && gDBView.msgFolder == folders[i] && folders[i].server.type != "imap")
- this._resetThreadPane();
-
folders[i].compact(null, msgWindow);
}
},
/**
* Compacts all folders for accounts that the given folders belong
* to, or all folders for accounts of the currently selected folders.
*
@@ -1686,29 +1698,16 @@ let gFolderTreeController = {
window.openDialog("chrome://messenger/content/virtualFolderProperties.xul",
"", "chrome,titlebar,modal,centerscreen",
{folder: folder, editExistingFolder: true,
onOKCallback: editVirtualCallback,
msgWindow: msgWindow});
},
/**
- * For certain folder commands, the thread pane needs to be invalidated, this
- * takes care of doing so.
- */
- _resetThreadPane: function ftc_resetThreadPane() {
- if (gDBView)
- gCurrentlyDisplayedMessage = gDBView.currentlyDisplayedMessage;
-
- ClearThreadPaneSelection();
- ClearThreadPane();
- ClearMessagePane();
- },
-
- /**
* Prompts for confirmation, if the user hasn't already chosen the "don't ask
* again" option.
*
* @param aCommand - the command to prompt for
*/
_checkConfirmationPrompt: function ftc_confirm(aCommand) {
const Cc = Components.classes;
const Ci = Components.interfaces;
--- a/mail/base/content/mail-offline.js
+++ b/mail/base/content/mail-offline.js
@@ -259,12 +259,11 @@ var MailOfflineMgr = {
}
},
/**
* private helper method called whenever we detect a change to the offline state
*/
mailOfflineStateChanged: function (aGoingOffline)
{
- gFolderJustSwitched = true;
this.updateOfflineUI(aGoingOffline);
}
};
--- a/mail/base/content/mail3PaneWindowCommands.js
+++ b/mail/base/content/mail3PaneWindowCommands.js
@@ -1,48 +1,47 @@
-# -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is Mozilla Communicator client code, released
-# March 31, 1998.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 1998-2000
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# Jan Varga <varga@nixcorp.com>
-# HÃ¥kan Waara <hwaara@gmail.com>
-# Magnus Melin <mkmelin+mozilla@iki.fi>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Jan Varga <varga@nixcorp.com>
+ * HÃ¥kan Waara <hwaara@gmail.com>
+ * Magnus Melin <mkmelin+mozilla@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
var gMessengerBundle = document.getElementById("bundle_messenger");
// Controller object for folder pane
var FolderPaneController =
{
supportsCommand: function(command)
{
@@ -103,17 +102,17 @@ var FolderPaneController =
if (!this.isCommandEnabled(command)) return;
switch ( command )
{
case "cmd_delete":
case "cmd_shiftDelete":
case "button_delete":
case "cmd_deleteFolder":
- gFolderTreeController.deleteFolder();
+ gFolderTreeController.deleteFolder();
break;
}
},
onEvent: function(event)
{
}
};
@@ -237,17 +236,17 @@ var DefaultController =
case "cmd_downloadFlagged":
case "cmd_downloadSelected":
case "cmd_synchronizeOffline":
return MailOfflineMgr.isOnline();
case "cmd_watchThread":
case "cmd_killThread":
case "cmd_killSubthread":
- return(isNewsURI(GetFirstSelectedMessage()));
+ return(gFolderDisplay.selectedMessageIsNews);
default:
return false;
}
},
isCommandEnabled: function(command)
{
@@ -257,45 +256,37 @@ var DefaultController =
switch ( command )
{
case "cmd_delete":
UpdateDeleteCommand();
// fall through
case "button_delete":
UpdateDeleteToolbarButton();
- if (gDBView)
- gDBView.getCommandStatus(nsMsgViewCommandType.deleteMsg, enabled, checkStatus);
- return enabled.value;
+ return gFolderDisplay.getCommandStatus(nsMsgViewCommandType.deleteMsg);
case "cmd_shiftDelete":
- if (gDBView)
- gDBView.getCommandStatus(nsMsgViewCommandType.deleteNoTrash, enabled, checkStatus);
- return enabled.value;
+ return gFolderDisplay.getCommandStatus(nsMsgViewCommandType.deleteNoTrash);
case "cmd_deleteFolder":
var folders = gFolderTreeView.getSelectedFolders();
if (folders.length == 1) {
var folder = folders[0];
if (folder.server.type == "nntp")
return false; // Just disable the command for news.
else
return CanDeleteFolder(folder);
}
return false;
case "button_junk":
UpdateJunkToolbarButton();
- if (gDBView)
- gDBView.getCommandStatus(nsMsgViewCommandType.junk, enabled, checkStatus);
- return enabled.value;
+ return gFolderDisplay.getCommandStatus(nsMsgViewCommandType.junk);
case "cmd_killThread":
case "cmd_killSubthread":
return GetNumSelectedMessages() > 0;
case "cmd_watchThread":
- if (gDBView)
- gDBView.getCommandStatus(nsMsgViewCommandType.toggleThreadWatched, enabled, checkStatus);
- return enabled.value;
+ return gFolderDisplay.getCommandStatus(nsMsgViewCommandType.toggleThreadWatched);
case "cmd_createFilterFromPopup":
case "cmd_createFilterFromMenu":
var loadedFolder = GetLoadedMsgFolder();
if (!(loadedFolder && loadedFolder.server.canHaveFilters))
return false; // else fall thru
case "cmd_saveAsFile":
case "cmd_saveAsTemplate":
if (GetNumSelectedMessages() > 1)
@@ -324,29 +315,30 @@ var DefaultController =
var whichText = "valueMessage";
if (GetNumSelectedMessages() > 1)
whichText = "valueSelection";
goSetMenuValue(command, whichText);
goSetAccessKey(command, whichText + "AccessKey");
}
if (GetNumSelectedMessages() > 0)
{
- if (gDBView)
- {
- gDBView.getCommandStatus(nsMsgViewCommandType.cmdRequiringMsgBody, enabled, checkStatus);
- return enabled.value;
- }
+ if (!gFolderDisplay.getCommandStatus(nsMsgViewCommandType.cmdRequiringMsgBody))
+ return false;
+ if (command == "cmd_reply" || command == "button_reply")
+ return IsReplyEnabled();
+ if (command == "cmd_replyall" || command == "button_replyall")
+ return IsReplyAllEnabled();
+ if (command == "cmd_replylist" || command == "button_replylist")
+ return IsReplyListEnabled();
+ return true;
}
return false;
case "cmd_printpreview":
- if ( GetNumSelectedMessages() == 1 && gDBView)
- {
- gDBView.getCommandStatus(nsMsgViewCommandType.cmdRequiringMsgBody, enabled, checkStatus);
- return enabled.value;
- }
+ if (GetNumSelectedMessages() == 1)
+ return gFolderDisplay.getCommandStatus(nsMsgViewCommandType.cmdRequiringMsgBody);
return false;
case "cmd_printSetup":
return true;
case "cmd_markAsFlagged":
case "button_file":
case "cmd_file":
case "cmd_archive":
return (GetNumSelectedMessages() > 0 );
@@ -355,33 +347,27 @@ var DefaultController =
let folder = GetLoadedMsgFolder();
return GetNumSelectedMessages() > 0 && folder &&
!(IsSpecialFolder(folder, Components.interfaces.nsMsgFolderFlags.Archive,
true));
}
case "cmd_markAsJunk":
case "cmd_markAsNotJunk":
// can't do news on junk yet.
- return (GetNumSelectedMessages() > 0 && !isNewsURI(GetFirstSelectedMessage()));
+ return (GetNumSelectedMessages() > 0 && !gFolderDisplay.selectedMessageIsNews);
case "cmd_recalculateJunkScore":
if (GetNumSelectedMessages() > 0)
- gDBView.getCommandStatus(nsMsgViewCommandType.runJunkControls, enabled, checkStatus);
- return enabled.value;
+ return gFolderDisplay.getCommandStatus(nsMsgViewCommandType.runJunkControls);
+ return false;
case "cmd_applyFilters":
- if (gDBView)
- gDBView.getCommandStatus(nsMsgViewCommandType.applyFilters, enabled, checkStatus);
- return enabled.value;
+ return gFolderDisplay.getCommandStatus(nsMsgViewCommandType.applyFilters);
case "cmd_runJunkControls":
- if (gDBView)
- gDBView.getCommandStatus(nsMsgViewCommandType.runJunkControls, enabled, checkStatus);
- return enabled.value;
+ return gFolderDisplay.getCommandStatus(nsMsgViewCommandType.runJunkControls);
case "cmd_deleteJunk":
- if (gDBView)
- gDBView.getCommandStatus(nsMsgViewCommandType.deleteJunk, enabled, checkStatus);
- return enabled.value;
+ return gFolderDisplay.getCommandStatus(nsMsgViewCommandType.deleteJunk);
case "button_mark":
case "cmd_tag":
case "cmd_markAsRead":
case "cmd_markThreadAsRead":
return GetNumSelectedMessages() > 0;
case "button_previous":
case "button_next":
return IsViewNavigationItemEnabled();
@@ -413,28 +399,27 @@ var DefaultController =
case "cmd_selectAll":
case "cmd_selectFlagged":
return gDBView != null;
// these are enabled on when we are in threaded mode
case "cmd_selectThread":
if (GetNumSelectedMessages() <= 0) return false;
case "cmd_expandAllThreads":
case "cmd_collapseAllThreads":
- return gDBView && (gDBView.viewFlags & nsMsgViewFlagsType.kThreadedDisplay);
+ return gFolderDisplay.view.showThreaded;
case "cmd_nextFlaggedMsg":
case "cmd_previousFlaggedMsg":
return IsViewNavigationItemEnabled();
case "cmd_viewAllMsgs":
case "cmd_viewUnreadMsgs":
case "cmd_viewIgnoredThreads":
return gDBView;
case "cmd_viewThreadsWithUnread":
case "cmd_viewWatchedThreadsWithUnread":
- return gDBView && !(GetSelectedMsgFolders()[0].flags &
- Components.interfaces.nsMsgFolderFlags.Virtual);
+ return !gFolderDisplay.view.isVirtual;
case "cmd_stop":
return true;
case "cmd_undo":
case "cmd_redo":
return SetupUndoRedoCommand(command);
case "cmd_renameFolder":
{
let folders = gFolderTreeView.getSelectedFolders();
@@ -481,17 +466,17 @@ var DefaultController =
}
case "cmd_setFolderCharset":
return IsFolderCharsetEnabled();
case "cmd_downloadFlagged":
return(IsFolderSelected() && MailOfflineMgr.isOnline());
case "cmd_downloadSelected":
return (IsFolderSelected() && MailOfflineMgr.isOnline() && GetNumSelectedMessages() > 0);
case "cmd_synchronizeOffline":
- return MailOfflineMgr.isOnline() && IsAccountOfflineEnabled();
+ return MailOfflineMgr.isOnline();
case "cmd_settingsOffline":
return IsAccountOfflineEnabled();
case "cmd_moveToFolderAgain":
// Disable "Move to <folder> Again" for news and other read only
// folders since we can't really move messages from there - only copy.
if (pref.getBoolPref("mail.last_msg_movecopy_was_move"))
{
let loadedFolder = gFolderTreeView.getSelectedFolders()[0];
@@ -564,36 +549,41 @@ var DefaultController =
break;
case "cmd_createFilterFromPopup":
break;// This does nothing because the createfilter is invoked from the popupnode oncommand.
case "button_delete":
case "cmd_delete":
// if the user deletes a message before its mark as read timer goes off, we should mark it as read
// this ensures that we clear the biff indicator from the system tray when the user deletes the new message
MarkSelectedMessagesRead(true);
- SetNextMessageAfterDelete();
- gDBView.doCommand(nsMsgViewCommandType.deleteMsg);
+ // If this is a right-click triggered delete, then do not hint about
+ // the deletion. Note: The code that swaps the selection back in will
+ // take care of ensuring that this deletion does not make the saved
+ // selection incorrect.
+ if (!gRightMouseButtonSavedSelection)
+ gFolderDisplay.hintAboutToDeleteMessages();
+ gFolderDisplay.doCommand(nsMsgViewCommandType.deleteMsg);
break;
case "cmd_shiftDelete":
MarkSelectedMessagesRead(true);
- SetNextMessageAfterDelete();
- gDBView.doCommand(nsMsgViewCommandType.deleteNoTrash);
+ gFolderDisplay.hintAboutToDeleteMessages();
+ gFolderDisplay.doCommand(nsMsgViewCommandType.deleteNoTrash);
break;
case "cmd_deleteFolder":
gFolderTreeController.deleteFolder();
break;
case "cmd_killThread":
/* kill thread kills the thread and then does a next unread */
GoNextMessage(nsMsgNavigationType.toggleThreadKilled, true);
break;
case "cmd_killSubthread":
GoNextMessage(nsMsgNavigationType.toggleSubthreadKilled, true);
break;
case "cmd_watchThread":
- gDBView.doCommand(nsMsgViewCommandType.toggleThreadWatched);
+ gFolderDisplay.doCommand(nsMsgViewCommandType.toggleThreadWatched);
break;
case "button_next":
case "cmd_nextUnreadMsg":
GoNextMessage(nsMsgNavigationType.nextUnreadMessage, true);
break;
case "cmd_nextUnreadThread":
GoNextMessage(nsMsgNavigationType.nextUnreadThread, true);
break;
@@ -634,20 +624,23 @@ var DefaultController =
break;
case "cmd_undo":
messenger.undo(msgWindow);
break;
case "cmd_redo":
messenger.redo(msgWindow);
break;
case "cmd_expandAllThreads":
- gDBView.doCommand(nsMsgViewCommandType.expandAll);
+ gFolderDisplay.doCommand(nsMsgViewCommandType.expandAll);
+ gFolderDisplay.ensureSelectionIsVisible();
break;
case "cmd_collapseAllThreads":
- gDBView.doCommand(nsMsgViewCommandType.collapseAll);
+ gFolderDisplay.selectSelectedThreadRoots();
+ gFolderDisplay.doCommand(nsMsgViewCommandType.collapseAll);
+ gFolderDisplay.ensureSelectionIsVisible();
break;
case "cmd_renameFolder":
gFolderTreeController.renameFolder();
return;
case "cmd_sendUnsentMsgs":
// if offline, prompt for sendUnsentMessages
if (MailOfflineMgr.isOnline())
SendUnsentMessages();
@@ -668,17 +661,17 @@ var DefaultController =
return;
case "cmd_saveAsFile":
MsgSaveAsFile();
return;
case "cmd_saveAsTemplate":
MsgSaveAsTemplate();
return;
case "cmd_viewPageSource":
- ViewPageSource(GetSelectedMessages());
+ ViewPageSource(gFolderDisplay.selectedMessageUris);
return;
case "cmd_setFolderCharset":
gFolderTreeController.editFolder();
return;
case "cmd_reload":
ReloadMessage();
return;
case "cmd_find":
@@ -709,20 +702,20 @@ var DefaultController =
MsgSearchMessages();
return;
case "button_mark":
case "cmd_markAsRead":
MsgMarkMsgAsRead();
return;
case "cmd_markThreadAsRead":
ClearPendingReadTimer();
- gDBView.doCommand(nsMsgViewCommandType.markThreadRead);
+ gFolderDisplay.doCommand(nsMsgViewCommandType.markThreadRead);
return;
case "cmd_markAllRead":
- gDBView.doCommand(nsMsgViewCommandType.markAllRead);
+ gFolderDisplay.doCommand(nsMsgViewCommandType.markAllRead);
return;
case "button_junk":
MsgJunk();
return;
case "cmd_stop":
msgWindow.StopUrls();
return;
case "cmd_markAsFlagged":
@@ -754,20 +747,20 @@ var DefaultController =
return;
case "cmd_compactFolder":
gFolderTreeController.compactAllFoldersForAccount();
return;
case "button_compact":
gFolderTreeController.compactFolders();
return;
case "cmd_downloadFlagged":
- gDBView.doCommand(nsMsgViewCommandType.downloadFlaggedForOffline);
+ gFolderDisplay.doCommand(nsMsgViewCommandType.downloadFlaggedForOffline);
break;
case "cmd_downloadSelected":
- gDBView.doCommand(nsMsgViewCommandType.downloadSelectedForOffline);
+ gFolderDisplay.doCommand(nsMsgViewCommandType.downloadSelectedForOffline);
break;
case "cmd_synchronizeOffline":
MsgSynchronizeOffline();
break;
case "cmd_settingsOffline":
MailOfflineMgr.openOfflineAccountSettings();
break;
case "cmd_moveToFolderAgain":
@@ -776,27 +769,23 @@ var DefaultController =
MsgMoveMessage(GetMsgFolderFromUri(folderId));
else
MsgCopyMessage(GetMsgFolderFromUri(folderId));
break;
case "cmd_selectAll":
// move the focus so the user can delete the newly selected messages, not the folder
SetFocusThreadPane();
// if in threaded mode, the view will expand all before selecting all
- gDBView.doCommand(nsMsgViewCommandType.selectAll)
- if (gDBView.numSelected != 1) {
- setTitleFromFolder(gDBView.msgFolder,null);
- ClearMessagePane();
- }
+ gFolderDisplay.doCommand(nsMsgViewCommandType.selectAll);
break;
case "cmd_selectThread":
- gDBView.doCommand(nsMsgViewCommandType.selectThread);
+ gFolderDisplay.doCommand(nsMsgViewCommandType.selectThread);
break;
case "cmd_selectFlagged":
- gDBView.doCommand(nsMsgViewCommandType.selectFlagged);
+ gFolderDisplay.doCommand(nsMsgViewCommandType.selectFlagged);
break;
case "cmd_fullZoomReduce":
ZoomManager.reduce();
break;
case "cmd_fullZoomEnlarge":
ZoomManager.enlarge();
break;
case "cmd_fullZoomReset":
--- a/mail/base/content/mailContextMenus.js
+++ b/mail/base/content/mailContextMenus.js
@@ -1,113 +1,77 @@
-# -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-#
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is Mozilla Communicator client code, released
-# March 31, 1998.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 2000
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# Jan Varga <varga@nixcorp.com>
-# Hakan Waara <hwaara@chello.se>
-# Markus Hossner <markushossner@gmx.de>
-# Magnus Melin <mkmelin+mozilla@iki.fi>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Jan Varga <varga@nixcorp.com>
+ * Hakan Waara <hwaara@chello.se>
+ * Markus Hossner <markushossner@gmx.de>
+ * Magnus Melin <mkmelin+mozilla@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
//NOTE: gMessengerBundle must be defined and set or this Overlay won't work
Components.utils.import("resource://gre/modules/PluralForm.jsm");
const mailtolength = 7;
/**
* Function to change the highlighted row back to the row that is currently
* outline/dotted without loading the contents of either rows. This is
* triggered when the context menu for a given row is hidden/closed
* (onpopuphiding).
* @param tree the tree element to restore selection for
*/
function RestoreSelectionWithoutContentLoad(tree)
{
- if (!tree)
- return;
-
- // If a delete or move command had been issued, then we should
- // reset gRightMouseButtonDown and gThreadPaneDeleteOrMoveOccurred
- // and return (see bug 142065).
- if(gThreadPaneDeleteOrMoveOccurred)
- {
- gRightMouseButtonDown = false;
- gThreadPaneDeleteOrMoveOccurred = false;
- return;
- }
-
- var treeSelection = tree.view.selection;
-
- // make sure that currentIndex is valid so that we don't try to restore
- // a selection of an invalid row.
- if((!treeSelection.isSelected(treeSelection.currentIndex)) &&
- (treeSelection.currentIndex >= 0))
- {
- treeSelection.selectEventsSuppressed = true;
- treeSelection.select(treeSelection.currentIndex);
- treeSelection.selectEventsSuppressed = false;
+ if (gRightMouseButtonSavedSelection) {
+ let view = gRightMouseButtonSavedSelection.view;
+ // restore the selection
+ let transientSelection = gRightMouseButtonSavedSelection.transientSelection;
+ let realSelection = gRightMouseButtonSavedSelection.realSelection;
+ view.selection = realSelection;
+ // replay any calls to adjustSelection, this handles suppression.
+ transientSelection.replayAdjustSelectionLog(realSelection);
+ gRightMouseButtonSavedSelection = null;
- // Keep track of which row in the thread pane is currently selected.
- // This is currently only needed when deleting messages. See
- // declaration of var in msgMail3PaneWindow.js.
- if(tree.id == "threadTree")
- gThreadPaneCurrentSelectedIndex = treeSelection.currentIndex;
- }
- else if(treeSelection.currentIndex < 0)
- // Clear the selection in the case of when a folder has just been
- // loaded where the message pane does not have a message loaded yet.
- // When right-clicking a message in this case and dismissing the
- // popup menu (by either executing a menu command or clicking
- // somewhere else), the selection needs to be cleared.
- // However, if the 'Delete Message' or 'Move To' menu item has been
- // selected, DO NOT clear the selection, else it will prevent the
- // tree view from refreshing.
- treeSelection.clearSelection();
-
- // Need to reset gRightMouseButtonDown to false here because
- // TreeOnMouseDown() is only called on a mousedown, not on a key down.
- // So resetting it here allows the loading of messages in the messagepane
- // when navigating via the keyboard or the toolbar buttons *after*
- // the context menu has been dismissed.
- gRightMouseButtonDown = false;
+ if (tree)
+ tree.treeBoxObject.invalidate();
+ }
}
/**
* Function to clear out the global nsContextMenu, and in the case when we
* were a threadpane context menu, restore the selection so that a right-click
* on a non-selected row doesn't move the selection.
* @param event the onpopuphiding event
*/
@@ -149,18 +113,18 @@ function fillMailContextMenu(event)
var numSelected = GetNumSelectedMessages();
if (numSelected == 0)
return false; // Don't show the context menu if no items are selected.
var inThreadPane = popupNodeIsInThreadPane();
gContextMenu = new nsContextMenu(event.target);
- var selectedMessage = GetFirstSelectedMessage();
- var isNewsgroup = IsNewsMessage(selectedMessage);
+ var selectedMessage = gFolderDisplay.selectedMessage;
+ var isNewsgroup = gFolderDisplay.selectedMessageIsNews;
// Clear the global var used to keep track if a 'Delete Message' or 'Move
// To' command has been triggered via the thread pane context menu.
gThreadPaneDeleteOrMoveOccurred = false;
// Don't show mail items for links/images, just show related items.
var hideMailItems = !inThreadPane &&
(gContextMenu.onImage || gContextMenu.onLink);
@@ -227,21 +191,20 @@ function fillMailContextMenu(event)
ShowMenuItem("paneContext-afterMove", !inThreadPane);
ShowMenuItem("mailContext-tags", !hideMailItems && msgFolder);
ShowMenuItem("mailContext-mark", !hideMailItems && msgFolder);
setSingleSelection("mailContext-saveAs");
-#ifdef XP_MACOSX
- ShowMenuItem("mailContext-printpreview", false);
-#else
- setSingleSelection("mailContext-printpreview");
-#endif
+ if (gPlatformOSX)
+ ShowMenuItem("mailContext-printpreview", false);
+ else
+ setSingleSelection("mailContext-printpreview");
ShowMenuItem("mailContext-print", !hideMailItems);
ShowMenuItem("mailContext-delete", !hideMailItems && (isNewsgroup || canMove));
// This function is needed for the case where a folder is just loaded (while
// there isn't a message loaded in the message pane), a right-click is done
// in the thread pane. This function will disable enable the 'Delete
// Message' menu item.
@@ -390,21 +353,19 @@ function OpenMessageForMessageId(message
function OpenMessageByHeader(messageHeader, openInNewWindow)
{
var folder = messageHeader.folder;
var folderURI = folder.URI;
if (openInNewWindow)
{
- var messageURI = folder.getUriForMsg(messageHeader);
-
window.openDialog("chrome://messenger/content/messageWindow.xul",
"_blank", "all,chrome,dialog=no,status,toolbar",
- messageURI, folderURI, null);
+ messageHeader);
}
else
{
if (msgWindow.openFolder != folderURI)
gFolderTreeView.selectFolder(folder);
var tree = null;
var wintype = document.documentElement.getAttribute('windowtype');
@@ -728,17 +689,17 @@ function SetMenuItemLabel(id, label)
}
// helper function used by shouldShowSeparator
function hasAVisibleNextSibling(aNode)
{
var sibling = aNode.nextSibling;
while (sibling)
{
- if (sibling.getAttribute("hidden") != "true"
+ if (sibling.getAttribute("hidden") != "true"
&& sibling.localName != "menuseparator")
return true;
sibling = sibling.nextSibling;
}
return false;
}
function IsMenuItemShowing(menuID)
@@ -770,24 +731,24 @@ function composeEmailTo ()
params.type = Components.interfaces.nsIMsgCompType.New;
params.format = Components.interfaces.nsIMsgCompFormat.Default;
params.identity = accountManager.getFirstIdentityForServer(GetLoadedMsgFolder().server);
params.composeFields = fields;
msgComposeService.OpenComposeWindowWithParams(null, params);
}
// Extracts email address from url string
-function getEmail (url)
+function getEmail (url)
{
var qmark = url.indexOf( "?" );
var addresses;
- if ( qmark > mailtolength )
+ if ( qmark > mailtolength )
addresses = url.substring( mailtolength, qmark );
- else
+ else
addresses = url.substr( mailtolength );
// Let's try to unescape it using a character set
try {
var characterSet = gContextMenu.target.ownerDocument.characterSet;
const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
.getService(Components.interfaces.nsITextToSubURI);
addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses);
}
--- a/mail/base/content/mailWidgets.xml
+++ b/mail/base/content/mailWidgets.xml
@@ -233,20 +233,20 @@
<xul:hbox class="headerValueBox" anonid="longEmailAddresses" flex="1"
singleline="true"
align="baseline"
onoverflow="this.parentNode.overflow(event)"
onunderflow="this.parentNode.underflow(event)">
<xul:description class="headerValue" containsEmail="true"
anonid="emailAddresses" flex="1"
orient="vertical" pack="start" />
- <xul:label class="moreIndicator" value="&more.label;" anonid="more"
- collapsed="true"
- onclick="this.parentNode.parentNode.toggleWrap()"/>
</xul:hbox>
+ <xul:label class="moreIndicator" value="&more.label;" anonid="more"
+ collapsed="true"
+ onclick="this.parentNode.toggleWrap()"/>
</content>
<implementation>
<constructor>
<![CDATA[
this.mAddresses = new Array;
]]>
</constructor>
--- a/mail/base/content/mailWindow.js
+++ b/mail/base/content/mailWindow.js
@@ -1,47 +1,46 @@
-# -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is Mozilla Communicator client code, released
-# March 31, 1998.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 1998-1999
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# Jan Varga <varga@nixcorp.com>
-# HÃ¥kan Waara <hwaara@gmail.com>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
+/** ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Jan Varga <varga@nixcorp.com>
+ * HÃ¥kan Waara <hwaara@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
Components.utils.import("resource://app/modules/appIdleManager.js");
//This file stores variables common to mail windows
var messenger;
var pref;
var statusFeedback;
var msgWindow;
@@ -54,40 +53,45 @@ var gBrandBundle;
Components.utils.import("resource://app/modules/gloda/log4moz.js");
var gContextMenu;
var gMailWindowLog = Log4Moz.getConfiguredLogger("mailWindow", Log4Moz.Level.Debug, Log4Moz.Level.Debug, Log4Moz.Level.Debug);
var gAccountCentralLoaded = true;
+/**
+ * Indicate whether we are running on Mac OS X. Our code is currently littered
+ * with #ifdef/#ifndef XP_MACOSX's that do not need to exist in js code. Use
+ * of preprocessing makes error line numbers misleading, complicates
+ * development because preprocessed files can't be symlinked when using
+ * --enable-chrome-format=symlink, etc.
+ */
+var gPlatformOSX =
+ (window.navigator.oscpu.substring(0, 3).toLowerCase() == "mac");
+
+/**
+ * Called by messageWindow.xul:onunload, the 'single message display window'.
+ *
+ * Also called by messenger.xul:onunload's (the 3-pane window inside of tabs
+ * window) unload function, OnUnloadMessenger.
+ */
function OnMailWindowUnload()
{
MailOfflineMgr.uninit();
ClearPendingReadTimer();
- var searchSession = GetSearchSession();
- if (searchSession)
- {
- removeGlobalListeners();
- if (gPreQuickSearchView) //close the cached pre quick search view
- gPreQuickSearchView.close();
- }
-
- var dbview = GetDBView();
- if (dbview) {
- dbview.close();
- }
+ // all dbview closing is handled by OnUnloadMessenger for the 3-pane (it closes
+ // the tabs which close their views) and OnUnloadMessageWindow for the
+ // standalone message window.
var mailSession = Components.classes["@mozilla.org/messenger/services/session;1"]
.getService(Components.interfaces.nsIMsgMailSession);
- mailSession.RemoveFolderListener(folderListener);
-
mailSession.RemoveMsgWindow(msgWindow);
- messenger.setWindow(null, null);
+ // the tabs have the FolderDisplayWidget close their 'messenger' instances for us
msgWindow.closeWindow();
window.MsgStatusFeedback.unload();
Components.classes["@mozilla.org/activity-manager;1"]
.getService(Components.interfaces.nsIActivityManager)
.removeListener(window.MsgStatusFeedback);
}
@@ -98,17 +102,17 @@ function CreateMailWindowGlobals()
messenger = Components.classes["@mozilla.org/messenger;1"]
.createInstance(Components.interfaces.nsIMessenger);
pref = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch2);
window.addEventListener("blur", appIdleManager.onBlur, false);
window.addEventListener("focus", appIdleManager.onFocus, false);
-
+
//Create windows status feedback
// set the JS implementation of status feedback before creating the c++ one..
window.MsgStatusFeedback = new nsMsgStatusFeedback();
// double register the status feedback object as the xul browser window implementation
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebNavigation)
.QueryInterface(Components.interfaces.nsIDocShellTreeItem).treeOwner
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
@@ -156,17 +160,17 @@ function InitMsgWindow()
}
// We're going to implement our status feedback for the mail window in JS now.
// the following contains the implementation of our status feedback object
function nsMsgStatusFeedback()
{
this._statusText = document.getElementById("statusText");
- this._progressBar = document.getElementById("statusbar-icon");
+ this._progressBar = document.getElementById("statusbar-icon");
this._progressBarContainer = document.getElementById("statusbar-progresspanel");
this._throbber = document.getElementById("navigator-throbber");
this._stopCmd = document.getElementById("cmd_stop");
this._activeProcesses = new Array();
}
nsMsgStatusFeedback.prototype =
{
@@ -351,17 +355,17 @@ nsMsgStatusFeedback.prototype =
this._progressBarContainer.removeAttribute('collapsed');
this._progressBarVisible = true;
}
}
else {
// Stop the bar spinning as we're not doing anything now.
this._progressBar.setAttribute("mode", "determined");
this._progressBar.value = 0;
- this._progressBar.label = "";
+ this._progressBar.label = "";
if (this._progressBarVisible) {
this._progressBarContainer.collapsed = true;
this._progressBarVisible = false;
}
}
},
@@ -437,25 +441,26 @@ nsMsgWindowCommands.prototype =
selectFolder: function(folderUri)
{
gFolderTreeView.selectFolder(GetMsgFolderFromUri(folderUri));
},
selectMessage: function(messageUri)
{
- SelectMessage(messageUri);
+ let msgHdr = messenger.msgHdrFromURI(messageUri);
+ gFolderDisplay.selectMessage(msgHdr);
},
clearMsgPane: function()
{
- if (gDBView)
- setTitleFromFolder(gDBView.msgFolder,null);
- else
- setTitleFromFolder(null,null);
+ // This call happens as part of a display decision made by the nsMsgDBView
+ // instance. Strictly speaking, we don't want this. I think davida's
+ // patch will change this, so we can figure it out after that lands if
+ // there are issues.
ClearMessagePane();
}
}
/**
* @returns the pref name to use for fetching the start page url. Every time the application version changes,
* return "mailnews.start_page.override_url". If this is the first time the application has been
* launched, return "mailnews.start_page.welcome_url". Otherwise return "mailnews.start_page.url".
@@ -475,17 +480,16 @@ function startPageUrlPref()
}
/**
* Loads the mail start page.
*/
function loadStartPage()
{
gMessageNotificationBar.clearMsgNotifications();
- ClearThreadPaneSelection();
let startpage = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
.getService(Components.interfaces.nsIURLFormatter)
.formatURLPref(startPageUrlPref());
if (startpage)
{
try {
let urifixup = Components.classes["@mozilla.org/docshell/urifixup;1"]
.getService(Components.interfaces.nsIURIFixup);
@@ -498,67 +502,50 @@ function loadStartPage()
}
}
else
{
GetMessagePaneFrame().location.href = "about:blank";
}
}
-// When the ThreadPane is hidden via the displayDeck, we should collapse the
-// elements that are only meaningful to the thread pane. When AccountCentral is
-// shown via the displayDeck, we need to switch the displayDeck to show the
-// accountCentralBox, and load the iframe in the AccountCentral box with
-// corresponding page.
-function ShowAccountCentral()
-{
- var accountBox = document.getElementById("accountCentralBox");
- document.getElementById("displayDeck").selectedPanel = accountBox;
- var prefName = "mailnews.account_central_page.url";
- var acctCentralPage = pref.getComplexValue(prefName,
- Components.interfaces.nsIPrefLocalizedString).data;
- window.frames["accountCentralPane"].location.href = acctCentralPage;
-}
-
-function ShowThreadPane()
-{
- document.getElementById("displayDeck").selectedPanel =
- document.getElementById("threadPaneBox");
-}
-
function ShowingThreadPane()
{
var threadPaneSplitter = document.getElementById("threadpane-splitter");
threadPaneSplitter.collapsed = false;
GetMessagePane().collapsed = (threadPaneSplitter.getAttribute("state") == "collapsed");
// XXX We need to force the tree to refresh its new height
// so that it will correctly scroll to the newest message
GetThreadTree().boxObject.height;
document.getElementById("key_toggleMessagePane").removeAttribute("disabled");
}
function HidingThreadPane()
{
- ClearThreadPane();
GetUnreadCountElement().hidden = true;
GetTotalCountElement().hidden = true;
GetMessagePane().collapsed = true;
document.getElementById("threadpane-splitter").collapsed = true;
document.getElementById("key_toggleMessagePane").setAttribute("disabled", "true");
}
// The zoom manager, view source and possibly some other functions still rely
// on the getBrowser function.
function getBrowser()
{
let tabmail = document.getElementById('tabmail');
return tabmail ? tabmail.getBrowserForSelectedTab() :
document.getElementById("messagepane");
}
+// When the ThreadPane is hidden via the displayDeck, we should collapse the
+// elements that are only meaningful to the thread pane. When AccountCentral is
+// shown via the displayDeck, we need to switch the displayDeck to show the
+// accountCentralBox, and load the iframe in the AccountCentral box with
+// corresponding page.
var gCurrentDisplayDeckId = "";
function ObserveDisplayDeckChange(event)
{
var selectedPanel = document.getElementById("displayDeck").selectedPanel;
var nowSelected = selectedPanel ? selectedPanel.id : null;
// onselect fires for every mouse click inside the deck, so ObserveDisplayDeckChange is getting called every time we click
// on a message in the thread pane. Only show / Hide elements if the selected deck is actually changing.
if (nowSelected != gCurrentDisplayDeckId)
@@ -579,33 +566,24 @@ function ObserveDisplayDeckChange(event)
}
}
// Given the server, open the twisty and the set the selection
// on inbox of that server.
// prompt if offline.
function OpenInboxForServer(server)
{
- ShowThreadPane();
gFolderTreeView.selectFolder(GetInboxFolder(server));
if (MailOfflineMgr.isOnline() || MailOfflineMgr.getNewMail()) {
if (server.type != "imap")
GetMessagesForInboxOnServer(server);
}
}
-function GetSearchSession()
-{
- if (("gSearchSession" in top) && gSearchSession)
- return gSearchSession;
- else
- return null;
-}
-
/** Update state of zoom type (text vs. full) menu item. */
function UpdateFullZoomMenu() {
var menuItem = document.getElementById("menu_fullZoomToggle");
menuItem.setAttribute("checked", !ZoomManager.useFullZoom);
}
/**
* This class implements nsIBadCertListener2. Its job is to prevent "bad cert"
--- a/mail/base/content/mailWindowOverlay.js
+++ b/mail/base/content/mailWindowOverlay.js
@@ -1,55 +1,55 @@
-# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is Mozilla Communicator client code, released
-# March 31, 1998.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 1998-1999
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# timeless
-# slucy@objectivesw.co.uk
-# HÃ¥kan Waara <hwaara@chello.se>
-# Jan Varga <varga@nixcorp.com>
-# Seth Spitzer <sspitzer@netscape.com>
-# David Bienvenu <bienvenu@nventure.com>
-# Karsten Düsterloh <mnyromyr@tprac.de>
-# Christopher Thomas <cst@yecc.com>
-# Jeremy Morton <bugzilla@game-point.net>
-# Andrew Sutherland <asutherland@asutherland.org>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * timeless
+ * slucy@objectivesw.co.uk
+ * HÃ¥kan Waara <hwaara@chello.se>
+ * Jan Varga <varga@nixcorp.com>
+ * Seth Spitzer <sspitzer@netscape.com>
+ * David Bienvenu <bienvenu@nventure.com>
+ * Karsten Düsterloh <mnyromyr@tprac.de>
+ * Christopher Thomas <cst@yecc.com>
+ * Jeremy Morton <bugzilla@game-point.net>
+ * Andrew Sutherland <asutherland@asutherland.org>
+ * Dan Mosedale <dmose@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
const ADDR_DB_LARGE_COMMIT = 1;
const kClassicMailLayout = 0;
const kWideMailLayout = 1;
const kVerticalMailLayout = 2;
// Per message header flags to keep track of whether the user is allowing remote
@@ -153,17 +153,17 @@ function InitEditMessagesMenu()
function InitGoMessagesMenu()
{
document.commandDispatcher.updateCommands('create-menu-go');
}
function view_init()
{
- var isFeed = IsFeedItem();
+ var isFeed = gFolderDisplay.selectedMessageIsFeed;
if (!gMessengerBundle)
gMessengerBundle = document.getElementById("bundle_messenger");
var messagePaneMenuItem = document.getElementById("menu_showMessage");
if (!messagePaneMenuItem.hidden) { // Hidden in the standalone msg window.
messagePaneMenuItem.setAttribute("checked", !IsMessagePaneCollapsed());
messagePaneMenuItem.disabled = gAccountCentralLoaded;
@@ -232,105 +232,106 @@ function InitViewFolderViewsMenu(event)
function setSortByMenuItemCheckState(id, value)
{
var menuitem = document.getElementById(id);
if (menuitem)
menuitem.setAttribute("checked", value);
}
+/**
+ * Called when showing the menu_viewSortPopup menupopup, so it should always
+ * be up-to-date.
+ */
function InitViewSortByMenu()
{
- var sortType = gDBView.sortType;
+ var sortType = gFolderDisplay.view.primarySortType;
setSortByMenuItemCheckState("sortByDateMenuitem", (sortType == nsMsgViewSortType.byDate));
setSortByMenuItemCheckState("sortByReceivedMenuitem", (sortType == nsMsgViewSortType.byReceived));
setSortByMenuItemCheckState("sortByFlagMenuitem", (sortType == nsMsgViewSortType.byFlagged));
setSortByMenuItemCheckState("sortByOrderReceivedMenuitem", (sortType == nsMsgViewSortType.byId));
setSortByMenuItemCheckState("sortByPriorityMenuitem", (sortType == nsMsgViewSortType.byPriority));
setSortByMenuItemCheckState("sortBySizeMenuitem", (sortType == nsMsgViewSortType.bySize));
setSortByMenuItemCheckState("sortByStatusMenuitem", (sortType == nsMsgViewSortType.byStatus));
setSortByMenuItemCheckState("sortBySubjectMenuitem", (sortType == nsMsgViewSortType.bySubject));
setSortByMenuItemCheckState("sortByUnreadMenuitem", (sortType == nsMsgViewSortType.byUnread));
setSortByMenuItemCheckState("sortByTagsMenuitem", (sortType == nsMsgViewSortType.byTags));
setSortByMenuItemCheckState("sortByJunkStatusMenuitem", (sortType == nsMsgViewSortType.byJunkStatus));
setSortByMenuItemCheckState("sortByFromMenuitem", (sortType == nsMsgViewSortType.byAuthor));
setSortByMenuItemCheckState("sortByRecipientMenuitem", (sortType == nsMsgViewSortType.byRecipient));
setSortByMenuItemCheckState("sortByAttachmentsMenuitem", (sortType == nsMsgViewSortType.byAttachments));
- var sortOrder = gDBView.sortOrder;
+ var sortOrder = gFolderDisplay.view.primarySortOrder;
var sortTypeSupportsGrouping = (sortType == nsMsgViewSortType.byAuthor ||
sortType == nsMsgViewSortType.byDate || sortType == nsMsgViewSortType.byReceived ||
sortType == nsMsgViewSortType.byPriority ||
sortType == nsMsgViewSortType.bySubject || sortType == nsMsgViewSortType.byTags ||
sortType == nsMsgViewSortType.byRecipient || sortType == nsMsgViewSortType.byAccount ||
sortType == nsMsgViewSortType.byStatus || sortType == nsMsgViewSortType.byFlagged ||
sortType == nsMsgViewSortType.byAttachments);
setSortByMenuItemCheckState("sortAscending", (sortOrder == nsMsgViewSortOrder.ascending));
setSortByMenuItemCheckState("sortDescending", (sortOrder == nsMsgViewSortOrder.descending));
- var grouped = ((gDBView.viewFlags & nsMsgViewFlagsType.kGroupBySort) != 0);
- var threaded = ((gDBView.viewFlags & nsMsgViewFlagsType.kThreadedDisplay) != 0 && !grouped);
+ var grouped = gFolderDisplay.view.showGroupedBySort;
+ var threaded = gFolderDisplay.view.showThreaded;
var sortThreadedMenuItem = document.getElementById("sortThreaded");
var sortUnthreadedMenuItem = document.getElementById("sortUnthreaded");
sortThreadedMenuItem.setAttribute("checked", threaded);
sortUnthreadedMenuItem.setAttribute("checked", !threaded && !grouped);
var groupBySortOrderMenuItem = document.getElementById("groupBySort");
groupBySortOrderMenuItem.setAttribute("disabled", !sortTypeSupportsGrouping);
groupBySortOrderMenuItem.setAttribute("checked", grouped);
}
function InitViewMessagesMenu()
{
- var viewFlags = (gDBView) ? gDBView.viewFlags : 0;
- var viewType = (gDBView) ? gDBView.viewType : 0;
-
document.getElementById("viewAllMessagesMenuItem").setAttribute("checked",
- (viewFlags & nsMsgViewFlagsType.kUnreadOnly) == 0 &&
- (viewType == nsMsgViewType.eShowAllThreads));
+ !gFolderDisplay.view.showUnreadOnly &&
+ !gFolderDisplay.view.specialView);
document.getElementById("viewUnreadMessagesMenuItem").setAttribute("checked",
- (viewFlags & nsMsgViewFlagsType.kUnreadOnly) != 0);
+ gFolderDisplay.view.showUnreadOnly);
document.getElementById("viewThreadsWithUnreadMenuItem").setAttribute("checked",
- viewType == nsMsgViewType.eShowThreadsWithUnread);
+ gFolderDisplay.view.specialViewThreadsWithUnread);
document.getElementById("viewWatchedThreadsWithUnreadMenuItem").setAttribute("checked",
- viewType == nsMsgViewType.eShowWatchedThreadsWithUnread);
+ gFolderDisplay.view.specialViewWatchedThreadsWithUnread);
document.getElementById("viewIgnoredThreadsMenuItem").setAttribute("checked",
- (viewFlags & nsMsgViewFlagsType.kShowIgnored) != 0);
+ gFolderDisplay.view.showIgnored);
}
function InitMessageMenu()
{
- var selectedMsg = GetFirstSelectedMessage();
- var isNews = IsNewsMessage(selectedMsg);
- var isFeed = IsFeedItem();
+ var selectedMsg = gFolderDisplay.selectedMessage;
+ var isNews = gFolderDisplay.selectedMessageIsNews;
+ var isFeed = gFolderDisplay.selectedMessageIsFeed;
// We show reply to Newsgroups only for news messages.
document.getElementById("replyNewsgroupMainMenu").hidden = !isNews;
// For mail messages we say reply. For news we say ReplyToSender.
document.getElementById("replyMainMenu").hidden = isNews;
document.getElementById("replySenderMainMenu").hidden = !isNews;
// We only kill and watch threads for news.
document.getElementById("threadItemsSeparator").hidden = !isNews;
document.getElementById("killThread").hidden = !isNews;
document.getElementById("killSubthread").hidden = !isNews;
document.getElementById("watchThread").hidden = !isNews;
// Disable the move and copy menus if there are no messages selected.
// Disable the move menu if we can't delete msgs from the folder.
- var msgFolder = GetLoadedMsgFolder();
+ var msgFolder = gFolderDisplay.displayedFolder;
var enableMenuItem = selectedMsg && msgFolder && msgFolder.canDeleteMessages;
document.getElementById("moveMenu").disabled = !enableMenuItem;
// Also disable copy when no folder is loaded (like for .eml files).
document.getElementById("copyMenu").disabled = !(selectedMsg && msgFolder);
initMoveToFolderAgainMenu(document.getElementById("moveToFolderAgain"));
@@ -411,17 +412,17 @@ function InitViewHeadersMenu()
menuitem.setAttribute("checked", "true");
}
function InitViewBodyMenu()
{
var html_as = 0;
var prefer_plaintext = false;
var disallow_classes = 0;
- var isFeed = IsFeedItem();
+ var isFeed = gFolderDisplay.selectedMessageIsFeed;
const defaultIDs = ["bodyAllowHTML",
"bodySanitized",
"bodyAsPlaintext"];
const rssIDs = ["bodyFeedSummaryAllowHTML",
"bodyFeedSummarySanitized",
"bodyFeedSummaryAsPlaintext"];
var menuIDs = isFeed ? rssIDs : defaultIDs;
try
@@ -465,45 +466,27 @@ function InitViewBodyMenu()
if (isFeed) {
AllowHTML_menuitem.hidden = !gShowFeedSummary;
Sanitized_menuitem.hidden = !gShowFeedSummary;
AsPlaintext_menuitem.hidden = !gShowFeedSummary;
document.getElementById("viewFeedSummarySeparator").hidden = !gShowFeedSummary;
}
}
-function IsNewsMessage(messageUri)
-{
- return (/^news-message:/.test(messageUri));
-}
-
-function IsImapMessage(messageUri)
-{
- return (/^imap-message:/.test(messageUri));
-}
-
-function IsFeedItem()
-{
- return (GetFirstSelectedMessage() &&
- ((gMsgFolderSelected &&
- gMsgFolderSelected.server.type == 'rss') ||
- 'content-base' in currentHeaderData));
-}
-
function SetMenuItemLabel(menuItemId, customLabel)
{
var menuItem = document.getElementById(menuItemId);
if (menuItem)
menuItem.setAttribute('label', customLabel);
}
function RemoveAllMessageTags()
{
- var selectedMsgUris = GetSelectedMessages();
- if (!selectedMsgUris.length)
+ var selectedMessages = gFolderDisplay.selectedMessages;
+ if (!selectedMessages.length)
return;
var messages = Components.classes["@mozilla.org/array;1"]
.createInstance(Components.interfaces.nsIMutableArray);
var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
.getService(Components.interfaces.nsIMsgTagService);
var tagArray = tagService.getAllTags({});
@@ -518,19 +501,19 @@ function RemoveAllMessageTags()
var prevHdrFolder = null;
// this crudely handles cross-folder virtual folders with selected messages
// that spans folders, by coalescing consecutive messages in the selection
// that happen to be in the same folder. nsMsgSearchDBView does this better,
// but nsIMsgDBView doesn't handle commands with arguments, and untag takes a
// key argument. Furthermore, we only delete legacy labels and known tags,
// keeping other keywords like (non)junk intact.
- for (var i = 0; i < selectedMsgUris.length; ++i)
+ for (var i = 0; i < selectedMessages.length; ++i)
{
- var msgHdr = messenger.msgHdrFromURI(selectedMsgUris[i]);
+ var msgHdr = selectedMessages[i];
msgHdr.label = 0; // remove legacy label
if (prevHdrFolder != msgHdr.folder)
{
if (prevHdrFolder)
prevHdrFolder.removeKeywordsFromMessages(messages, allKeys);
messages.clear();
prevHdrFolder = msgHdr.folder;
}
@@ -542,17 +525,17 @@ function RemoveAllMessageTags()
}
function ToggleMessageTagKey(index)
{
if (GetNumSelectedMessages() < 1)
return;
// set the tag state based upon that of the first selected message,
// just like we do for markAsRead etc.
- var msgHdr = gDBView.hdrForFirstSelectedMessage;
+ var msgHdr = gFolderDisplay.selectedMessage;
var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
.getService(Components.interfaces.nsIMsgTagService);
var tagArray = tagService.getAllTags({});
for (var i = 0; i < tagArray.length; ++i)
{
var key = tagArray[i].key;
if (!--index)
{
@@ -575,27 +558,27 @@ function ToggleMessageTagMenu(target)
}
function ToggleMessageTag(key, addKey)
{
var messages = Components.classes["@mozilla.org/array;1"]
.createInstance(Components.interfaces.nsIMutableArray);
var msg = Components.classes["@mozilla.org/array;1"]
.createInstance(Components.interfaces.nsIMutableArray);
- var selectedMsgUris = GetSelectedMessages();
+ var selectedMessages = gFolderDisplay.selectedMessages;
var toggler = addKey ? "addKeywordsToMessages" : "removeKeywordsFromMessages";
var prevHdrFolder = null;
// this crudely handles cross-folder virtual folders with selected messages
// that spans folders, by coalescing consecutive msgs in the selection
// that happen to be in the same folder. nsMsgSearchDBView does this
// better, but nsIMsgDBView doesn't handle commands with arguments,
// and (un)tag takes a key argument.
- for (var i = 0; i < selectedMsgUris.length; ++i)
+ for (var i = 0; i < selectedMessages.length; ++i)
{
- var msgHdr = messenger.msgHdrFromURI(selectedMsgUris[i]);
+ var msgHdr = selectedMessages[i];
if (msgHdr.label)
{
// Since we touch all these messages anyway, migrate the label now.
// If we don't, the thread tree won't always show the correct tag state,
// because resetting a label doesn't update the tree anymore...
msg.clear();
msg.appendElement(msgHdr, false);
msgHdr.folder.addKeywordsToMessages(msg, "$label" + msgHdr.label);
@@ -643,17 +626,17 @@ function AddTagCallback(name, color)
function SetMessageTagLabel(menuitem, index, name)
{
// if a <key> is defined for this tag, use its key as the accesskey
// (the key for the tag at index n needs to have the id key_tag<n>)
var shortcutkey = document.getElementById("key_tag" + index);
var accesskey = shortcutkey ? shortcutkey.getAttribute("key") : "";
if (accesskey)
menuitem.setAttribute("accesskey", accesskey);
- var label = gMessengerBundle.getFormattedString("mailnews.tags.format",
+ var label = gMessengerBundle.getFormattedString("mailnews.tags.format",
[accesskey, name]);
menuitem.setAttribute("label", label);
}
function InitMessageTags(menuPopup)
{
var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
.getService(Components.interfaces.nsIMsgTagService);
@@ -668,17 +651,17 @@ function InitMessageTags(menuPopup)
// hide double menuseparator
menuseparator.previousSibling.hidden = !tagCount;
// create label and accesskey for the static remove item
var tagRemoveLabel = gMessengerBundle.getString("mailnews.tags.remove");
SetMessageTagLabel(menuPopup.firstChild, 0, tagRemoveLabel);
// now rebuild the list
- var msgHdr = gDBView.hdrForFirstSelectedMessage;
+ var msgHdr = gFolderDisplay.selectedMessage;
var curKeys = msgHdr.getStringProperty("keywords");
if (msgHdr.label)
curKeys += " $label" + msgHdr.label;
for (var i = 0; i < tagCount; ++i)
{
var taginfo = tagArray[i];
// TODO we want to either remove or "check" the tags that already exist
@@ -686,17 +669,17 @@ function InitMessageTags(menuPopup)
SetMessageTagLabel(newMenuItem, i + 1, taginfo.tag);
newMenuItem.setAttribute("value", taginfo.key);
newMenuItem.setAttribute("type", "checkbox");
var removeKey = (" " + curKeys + " ").indexOf(" " + taginfo.key + " ") > -1;
newMenuItem.setAttribute('checked', removeKey);
newMenuItem.setAttribute('oncommand', 'ToggleMessageTagMenu(event.target);');
var color = taginfo.color;
if (color)
- newMenuItem.setAttribute("class", "lc-" + color.substr(1));
+ newMenuItem.setAttribute("class", "lc-" + color.substr(1));
menuPopup.insertBefore(newMenuItem, menuseparator);
}
}
function backToolbarMenu_init(menuPopup)
{
populateHistoryMenu(menuPopup, true);
}
@@ -722,38 +705,43 @@ function populateHistoryMenu(menuPopup,
var numEntries = new Object;
var historyEntries = new Object;
messenger.getNavigateHistory(curPos, numEntries, historyEntries);
curPos.value = curPos.value * 2;
navDebug("curPos = " + curPos.value + " numEntries = " + numEntries.value + "\n");
var historyArray = historyEntries.value;
var folder;
var newMenuItem;
- if (GetLoadedMessage())
+ if (gFolderDisplay.selectedMessage)
{
if (!isBackMenu)
curPos.value += 2;
else
curPos.value -= 2;
}
// For populating the back menu, we want the most recently visited
// messages first in the menu. So we go backward from curPos to 0.
// For the forward menu, we want to go forward from curPos to the end.
var relPos = 0;
for (var i = curPos.value; (isBackMenu) ? i >= 0 : i < historyArray.length; i += ((isBackMenu) ? -2 : 2))
{
navDebug("history[" + i + "] = " + historyArray[i] + "\n");
navDebug("history[" + i + "] = " + historyArray[i + 1] + "\n");
- folder = GetMsgFolderFromUri(historyArray[i + 1])
+ folder = GetMsgFolderFromUri(historyArray[i + 1]);
navDebug("folder URI = " + folder.URI + "pretty name " + folder.prettyName + "\n");
var menuText = "";
+ // If the message was not being displayed via the current folder, prepend
+ // the folder name. We do not need to check underlying folders for
+ // virtual folders because 'folder' is the display folder, not the
+ // underlying one.
+ if (folder != gFolderDisplay.displayedFolder)
+ menuText = folder.prettyName + " - ";
+
var msgHdr = messenger.msgHdrFromURI(historyArray[i]);
- if (!IsCurrentLoadedFolder(folder))
- menuText = folder.prettyName + " - ";
var subject = "";
if (msgHdr.flags & Components.interfaces.nsMsgMessageFlags.HasRe)
subject = "Re: ";
if (msgHdr.mime2DecodedSubject)
subject += msgHdr.mime2DecodedSubject;
if (subject)
menuText += subject + " - ";
@@ -766,27 +754,38 @@ function populateHistoryMenu(menuPopup,
newMenuItem.folder = folder;
newMenuItem.setAttribute('oncommand', 'NavigateToUri(event.target); event.stopPropagation();');
menuPopup.appendChild(newMenuItem);
if (! (relPos % 20))
break;
}
}
+/**
+ * This is triggered by the history navigation menu options, as created by
+ * populateHistoryMenu above.
+ */
function NavigateToUri(target)
{
var historyIndex = target.getAttribute('value');
var msgUri = messenger.getMsgUriAtNavigatePos(historyIndex);
var folder = target.folder;
var msgHdr = messenger.msgHdrFromURI(msgUri);
navDebug("navigating from " + messenger.navigatePos + " by " + historyIndex + " to " + msgUri + "\n");
// this "- 0" seems to ensure that historyIndex is treated as an int, not a string.
messenger.navigatePos += (historyIndex - 0);
- LoadNavigatedToMessage(msgHdr, folder, folder.URI);
+
+ if (gFolderDisplay.displayedFolder != folder) {
+ if (gFolderTreeView)
+ gFolderTreeView.selectFolder(folder);
+ else
+ gFolderDisplay.show(folder);
+ }
+ gFolderDisplay.selectMessage(msgHdr);
}
function forwardToolbarMenu_init(menuPopup)
{
populateHistoryMenu(menuPopup, false);
}
function InitMessageMark()
@@ -802,107 +801,156 @@ function InitMessageMark()
function UpdateJunkToolbarButton()
{
var junkButtonDeck = document.getElementById("junk-deck");
if (junkButtonDeck)
junkButtonDeck.selectedIndex = SelectedMessagesAreJunk() ? 1 : 0;
}
-function UpdateReplyButtons()
+/**
+ * Should the reply command/button be enabled?
+ *
+ * @return whether the reply command/button should be enabled.
+ */
+function IsReplyEnabled()
{
- let msgHdr = messenger.msgHdrFromURI(GetLoadedMessage());
+ // If we're in an rss item, we never want to Reply, because there's
+ // usually no-one useful to reply to.
+ return !gFolderDisplay.selectedMessageIsFeed;
+}
+
+/**
+ * Should the reply-all command/button be enabled?
+ *
+ * @return whether the reply-all command/button should be enabled.
+ */
+function IsReplyAllEnabled()
+{
+ if (gFolderDisplay.selectedMessageIsNews)
+ // If we're in a news item, we always want ReplyAll, because we can
+ // reply to the sender and the newsgroup.
+ return true;
+ if (gFolderDisplay.selectedMessageIsFeed)
+ // If we're in an rss item, we never want to ReplyAll, because there's
+ // usually no-one useful to reply to.
+ return false;
+
+ let msgHdr = gFolderDisplay.selectedMessage;
let myEmail = getIdentityForHeader(msgHdr).email;
- let recipients = msgHdr.recipients + "," + msgHdr.ccList;
-
- // If my email address isn't in the to or cc list, then I've been bcc-ed.
- let imBcced = recipients.indexOf(myEmail) == -1;
-
- // Now, let's get the number of unique recipients
+ let addresses = msgHdr.author + "," + msgHdr.recipients + "," + msgHdr.ccList;
+
+ // If we've got any BCCed addresses (because we sent the message), add
+ // them as well.
+ if ("bcc" in currentHeaderData)
+ addresses += currentHeaderData.bcc.headerValue;
+
+ // Check to see if my email address is in the list of addresses.
+ let imInAddresses = addresses.indexOf(myEmail) != -1;
+
+ // Now, let's get the number of unique addresses.
let hdrParser = Components.classes["@mozilla.org/messenger/headerparser;1"]
.getService(Components.interfaces.nsIMsgHeaderParser);
- let uniqueRecipients = hdrParser.removeDuplicateAddresses(recipients, {});
- let numAddresses = hdrParser.parseHeadersWithArray(uniqueRecipients, {}, {}, {});
-
- // If I've been bcc-ed, then add 1 to the number of addresses to compensate.
- if (imBcced)
- numAddresses++
-
- // By default, ReplyAll if there is more than 1 person to reply to.
- let showReplyAll = numAddresses > 1;
-
- // And ReplyToList if there is a List-Post header.
- let showReplyList = currentHeaderData["list-post"];
-
- // Get the server type.
- let serverType = null;
- try
+ let uniqueAddresses = hdrParser.removeDuplicateAddresses(addresses, "");
+ let emailAddresses = {};
+ let numAddresses = hdrParser.parseHeadersWithArray(uniqueAddresses,
+ emailAddresses, {}, {});
+
+ // XXX: This should be handled by the nsIMsgHeaderParser. See Bug 498480.
+ // Remove addresses that look like email groups, because we don't support
+ // those yet. (Any address with a : in it will be an empty email group,
+ // or the colon and the groupname would be set as the first name, and not
+ // show up in the address at all.)
+ for (var i in emailAddresses.value)
{
- serverType = msgHdr.folder.server.type;
+ if (/:/.test(emailAddresses.value[i]))
+ numAddresses--;
}
- catch (ex)
- {
- // This empty catch block needs to be here because msgHdr.folder will
- // throw an exception when you try to access it on a .eml file.
- }
-
- // But, if we're in a news item, we should default to Reply.
- if (serverType == "nntp")
+
+ // I don't want to count my address in the number of addresses to reply
+ // to, since I won't be emailing myself.
+ if (imInAddresses)
+ numAddresses--;
+
+ // ReplyAll is enabled if there is more than 1 person to reply to.
+ return numAddresses > 1;
+}
+
+/**
+ * Should the reply-list command/button be enabled?
+ *
+ * @return whether the reply-list command/button should be enabled.
+ */
+function IsReplyListEnabled()
+{
+ // ReplyToList is enabled if there is a List-Post header.
+ return currentHeaderData["list-post"] != null;
+}
+
+/**
+ * Update the enabled/disabled states of the Reply, Reply-All, and
+ * Reply-List buttons. (After this function runs, one of the buttons
+ * should be shown, and the others should be hidden.)
+ */
+function UpdateReplyButtons()
+{
+ let showReplyAll = IsReplyAllEnabled();
+ let showReplyList = IsReplyListEnabled();
+
+ // If we're in a news item, we should default to Reply.
+ if (gFolderDisplay.selectedMessageIsNews)
{
showReplyAll = false;
showReplyList = false;
}
let buttonToShow = "reply";
if (showReplyList)
buttonToShow = "replyList";
else if (showReplyAll)
buttonToShow = "replyAll";
- let buttonBox = document.getElementById(gCollapsedHeaderViewMode ?
- "collapsedButtonBox" : "expandedButtonBox");
+ let buttonBox = getCurrentMsgHdrButtonBox();
let replyButton = buttonBox.getButton("hdrReplyButton");
let replyAllButton = buttonBox.getButton("hdrReplyAllButton");
let replyAllSubButton = buttonBox.getButton("hdrReplyAllSubButton");
let replyAllSubButtonSep = buttonBox.getButton("hdrReplyAllSubButtonSep");
let replyListButton = buttonBox.getButton("hdrReplyListButton");
replyButton.hidden = (buttonToShow != "reply");
replyAllButton.hidden = (buttonToShow != "replyAll");
replyListButton.hidden = (buttonToShow != "replyList");
- let replyListMenu = document.getElementById("menu_replyToList");
- replyListMenu.hidden = !showReplyList;
-
- let replyListCommand = document.getElementById("cmd_replylist");
- replyListCommand.disabled = !showReplyList;
-
- if (serverType == "nntp")
+ if (gFolderDisplay.selectedMessageIsNews)
{
// If it's a news item, show the ReplyAll sub-button and separator.
replyAllSubButton.hidden = false;
replyAllSubButtonSep.hidden = false;
}
- else if (serverType == "rss")
+ else if (gFolderDisplay.selectedMessageIsFeed)
{
// otherwise, if it's an rss item, hide all the Reply buttons.
replyButton.hidden = true;
replyAllButton.hidden = true;
replyListButton.hidden = true;
replyAllSubButton.hidden = true;
replyAllSubButtonSep.hidden = true;
}
else
{
// otherwise, hide the ReplyAll sub-buttons.
replyAllSubButton.hidden = true;
replyAllSubButtonSep.hidden = true;
}
+
+ goUpdateCommand("button_reply");
+ goUpdateCommand("button_replyall");
+ goUpdateCommand("button_replylist");
}
function UpdateDeleteToolbarButton()
{
var deleteButtonDeck = document.getElementById("delete-deck");
if (!deleteButtonDeck)
return;
@@ -913,57 +961,57 @@ function UpdateDeleteToolbarButton()
GetNumSelectedMessages() == 0)
deleteButtonDeck.selectedIndex = 0;
else
deleteButtonDeck.selectedIndex = SelectedMessagesAreDeleted() ? 1 : 0;
}
function UpdateDeleteCommand()
{
var value = "value";
- var uri = GetFirstSelectedMessage();
- if (IsNewsMessage(uri))
+ if (gFolderDisplay.selectedMessageIsNews)
value += "News";
else if (SelectedMessagesAreDeleted())
value += "IMAPDeleted";
if (GetNumSelectedMessages() < 2)
value += "Message";
else
value += "Messages";
goSetMenuValue("cmd_delete", value);
goSetAccessKey("cmd_delete", value + "AccessKey");
}
function SelectedMessagesAreDeleted()
{
- return gDBView && gDBView.numSelected &&
- (gDBView.hdrForFirstSelectedMessage.flags &
+ let firstSelectedMessage = gFolderDisplay.selectedMessage;
+ return firstSelectedMessage &&
+ (firstSelectedMessage.flags &
Components.interfaces.nsMsgMessageFlags.IMAPDeleted);
}
function SelectedMessagesAreJunk()
{
try {
- var junkScore = gDBView.hdrForFirstSelectedMessage.getStringProperty("junkscore");
+ var junkScore = gFolderDisplay.selectedMessage.getStringProperty("junkscore");
return (junkScore != "") && (junkScore != "0");
}
catch (ex) {
return false;
}
}
function SelectedMessagesAreRead()
{
- return gDBView && gDBView.numSelected &&
- gDBView.hdrForFirstSelectedMessage.isRead;
+ let firstSelectedMessage = gFolderDisplay.selectedMessage;
+ return firstSelectedMessage && firstSelectedMessage.isRead;
}
function SelectedMessagesAreFlagged()
{
- return gDBView && gDBView.numSelected &&
- gDBView.hdrForFirstSelectedMessage.isFlagged;
+ let firstSelectedMessage = gFolderDisplay.selectedMessage;
+ return firstSelectedMessage && firstSelectedMessage.isFlagged;
}
function GetFirstSelectedMsgFolder()
{
var selectedFolders = GetSelectedMsgFolders();
return (selectedFolders.length > 0) ? selectedFolders[0] : null;
}
@@ -1092,20 +1140,20 @@ function MsgGetNextNMessages()
if (MailOfflineMgr.isOnline() || MailOfflineMgr.getNewMail())
GetNextNMessages(GetFirstSelectedMsgFolder());
}
function MsgDeleteMessage(reallyDelete, fromToolbar)
{
// If from the toolbar, return right away if this is a news message
// only allow cancel from the menu: "Edit | Cancel / Delete Message".
- if (fromToolbar && isNewsURI(GetLoadedMsgFolder().URI))
+ if (fromToolbar && gFolderDisplay.view.isNewsFolder)
return;
- SetNextMessageAfterDelete();
+ gFolderDisplay.hintAboutToDeleteMessages();
if (reallyDelete)
gDBView.doCommand(nsMsgViewCommandType.deleteNoTrash);
else
gDBView.doCommand(nsMsgViewCommandType.deleteMsg);
}
/**
* Copies the selected messages to the destination folder
@@ -1124,17 +1172,17 @@ function MsgCopyMessage(aDestFolder)
*/
function MsgMoveMessage(aDestFolder)
{
// We don't move news messages, we copy them.
if (isNewsURI(gDBView.msgFolder.URI))
gDBView.doCommandWithFolder(nsMsgViewCommandType.copyMessages, aDestFolder);
else
{
- SetNextMessageAfterDelete();
+ gFolderDisplay.hintAboutToDeleteMessages();
gDBView.doCommandWithFolder(nsMsgViewCommandType.moveMessages, aDestFolder);
}
pref.setCharPref("mail.last_msg_movecopy_target_uri", aDestFolder.URI);
pref.setBoolPref("mail.last_msg_movecopy_was_move", true);
}
/**
* Calls the ComposeMessage function with the desired type, and proper default
@@ -1142,32 +1190,34 @@ function MsgMoveMessage(aDestFolder)
*
* @param aCompType the nsIMsgCompType to pass to the function
* @param aEvent (optional) the event that triggered the call
*/
function composeMsgByType(aCompType, aEvent) {
if (aEvent && aEvent.shiftKey) {
ComposeMessage(aCompType,
Components.interfaces.nsIMsgCompFormat.OppositeOfDefault,
- GetFirstSelectedMsgFolder(), GetSelectedMessages());
+ GetFirstSelectedMsgFolder(),
+ gFolderDisplay.selectedMessageUris);
}
else {
ComposeMessage(aCompType, Components.interfaces.nsIMsgCompFormat.Default,
- GetFirstSelectedMsgFolder(), GetSelectedMessages());
+ GetFirstSelectedMsgFolder(),
+ gFolderDisplay.selectedMessageUris);
}
}
function MsgNewMessage(event)
{
composeMsgByType(Components.interfaces.nsIMsgCompType.New, event);
}
function MsgReplyMessage(event)
{
- var loadedFolder = GetLoadedMsgFolder();
+ var loadedFolder = gFolderDisplay.displayedFolder;
if (loadedFolder)
{
var server = loadedFolder.server;
if(server && server.type == "nntp")
{
MsgReplyGroup(event);
return;
}
@@ -1202,50 +1252,47 @@ function BatchMessageMover()
this._batches = {};
this._currentKey = null;
}
BatchMessageMover.prototype = {
archiveSelectedMessages: function()
{
- // subtle:
- SetNextMessageAfterDelete(); // we're just pretending
- this.messageToSelectAfterWereDone = gNextMessageViewIndexAfterDelete;
- gNextMessageViewIndexAfterDelete = -2;
-
- let selectedMsgUris = GetSelectedMessages();
- if (!selectedMsgUris.length)
+ gFolderDisplay.hintMassMoveStarting();
+
+ let selectedMessages = gFolderDisplay.selectedMessages;
+ if (!selectedMessages.length)
return;
-
+
let messages = Components.classes["@mozilla.org/array;1"]
.createInstance(Components.interfaces.nsIMutableArray);
-
- for (let i = 0; i < selectedMsgUris.length; ++i)
+
+ for (let i = 0; i < selectedMessages.length; ++i)
{
- let msgHdr = messenger.msgHdrFromURI(selectedMsgUris[i]);
-
+ let msgHdr = selectedMessages[i];
+
let rootFolder = msgHdr.folder.server.rootFolder;
-
+
let msgDate = new Date(msgHdr.date / 1000); // convert date to JS date object
let msgYear = msgDate.getFullYear().toString();
let monthFolderName = msgDate.toLocaleFormat("%Y-%m")
let dstFolderName = monthFolderName;
let copyBatchKey = msgHdr.folder.URI + '\000' + dstFolderName;
if (! (copyBatchKey in this._batches)) {
this._batches[copyBatchKey] = [msgHdr.folder, msgYear, dstFolderName];
}
this._batches[copyBatchKey].push(msgHdr);
}
// Now we launch the code that will iterate over all of the message copies
// one in turn
this.processNextBatch();
},
-
+
processNextBatch: function()
{
for (let key in this._batches)
{
this._currentKey = key;
let batch = this._batches[key];
let srcFolder = batch[0];
let msgYear = batch[1];
@@ -1256,18 +1303,18 @@ BatchMessageMover.prototype = {
// rss servers don't have an identity so we special case the archives URI
let archiveFolderUri = (srcFolder.server.type == 'rss')
? srcFolder.server.serverURI + "/Archives"
: getIdentityForHeader(msgs[0], Ci.nsIMsgCompType
.ReplyAll).archiveFolder;
let archiveFolder = GetMsgFolderFromUri(archiveFolderUri, false);
let granularity = archiveFolder.server.archiveGranularity;
- // for imap folders, we need to create the sub-folders asynchronously,
- // so we chain the urls using the listener called back from
+ // for imap folders, we need to create the sub-folders asynchronously,
+ // so we chain the urls using the listener called back from
// createStorageIfMissing. For local, creatStorageIfMissing is
// synchronous.
let isImap = archiveFolder.server.type == "imap";
if (!archiveFolder.parent) {
archiveFolder.createStorageIfMissing(this);
if (isImap)
return;
}
@@ -1349,28 +1396,20 @@ BatchMessageMover.prototype = {
this._currentKey = null;
// is there a safe way to test whether this._batches is empty?
let empty = true;
for (let key in this._batches) {
empty = false;
}
- if (empty)
- {
- // we're just going to select the message now
- var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
- var treeSelection = treeView.selection;
- treeSelection.select(this.messageToSelectAfterWereDone);
- treeView.selectionChanged();
- }
- else
- {
+ if (!empty)
this.processNextBatch();
- }
+ else // this will select the appropriate next message
+ gFolderDisplay.hintMassMoveCompleted();
}
},
QueryInterface: function(iid) {
if (!iid.equals(Components.interfaces.nsIUrlListener) &&
!iid.equals(Components.interfaces.nsIMsgCopyServiceListener) &&
!iid.equals(Components.interfaces.nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
@@ -1415,28 +1454,26 @@ function MsgForwardAsInline(event)
function MsgEditMessageAsNew()
{
composeMsgByType(Components.interfaces.nsIMsgCompType.Template);
}
function MsgComposeDraftMessage()
{
- var loadedFolder = GetLoadedMsgFolder();
- var messageArray = GetSelectedMessages();
-
ComposeMessage(Components.interfaces.nsIMsgCompType.Draft,
Components.interfaces.nsIMsgCompFormat.Default,
- loadedFolder, messageArray);
+ gFolderDisplay.displayedFolder,
+ gFolderDisplay.selectedMessageUris);
}
function MsgCreateFilter()
{
// retrieve Sender direct from selected message's headers
- var msgHdr = gDBView.hdrForFirstSelectedMessage;
+ var msgHdr = gFolderDisplay.selectedMessage;
var headerParser = Components.classes["@mozilla.org/messenger/headerparser;1"]
.getService(Components.interfaces.nsIMsgHeaderParser);
var emailAddress = headerParser.extractHeaderAddressMailboxes(msgHdr.author);
if (emailAddress)
top.MsgFilters(emailAddress, null);
}
function MsgNewFolder(callBackFunctionName)
@@ -1552,24 +1589,24 @@ function ToggleFavoriteFolderFlag()
{
var folder = GetFirstSelectedMsgFolder();
folder.toggleFlag(Components.interfaces.nsMsgFolderFlags.Favorite);
}
function MsgSaveAsFile()
{
if (GetNumSelectedMessages() == 1)
- SaveAsFile(GetFirstSelectedMessage());
+ SaveAsFile(gFolderDisplay.selectedMessageUris[0]);
}
function MsgSaveAsTemplate()
{
- var folder = GetLoadedMsgFolder();
if (GetNumSelectedMessages() == 1)
- SaveAsTemplate(GetFirstSelectedMessage(), folder);
+ SaveAsTemplate(gFolderDisplay.selectedMessageUris[0],
+ gFolderDisplay.displayedFolder);
}
function CreateToolbarTooltip(document, event)
{
event.stopPropagation();
var tn = document.tooltipNode;
if (tn.localName != "tab")
return false; // Not a tab, so cancel the tooltip.
@@ -1580,315 +1617,337 @@ function CreateToolbarTooltip(document,
if (tn.hasAttribute("label")) {
event.target.setAttribute("label", tn.getAttribute("label"));
return true;
}
return false;
}
/**
- * mailTabType provides both "folder" and "message" tab modes. Under the
- * previous TabOwner framework, their logic was separated into two 'classes'
- * which called common helper methods and had similar boilerplate logic.
+ * Displays message "folder"s, mail "message"s, and "glodaSearch" results. The
+ * commonality is that they all use the "mailContent" panel's folder tree,
+ * thread tree, and message pane objects. This happens for historical reasons,
+ * likely involving the fact that prior to the introduction of this
+ * abstraction, everything was always stored in global objects. For the 3.0
+ * release cycle we considered avoiding this 'multiplexed' style of operation
+ * but decided against moving to making each tab be indepdendent because of
+ * presumed complexity.
+ *
+ * The tab info objects (as tabmail's currentTabInfo/tabInfo fields contain)
+ * have the following attributes specific to our implementation:
+ *
+ *
+ * @property {string} uriToOpen
+ * @property {nsIMsgFolder} msgSelectedFolder Preserves gMsgFolderSelected
+ * global.
+ * @property {nsIMsgDBView} dbView The database view to use with the thread tree
+ * when this tab is displayed. The value will be assigned to the global
+ * gDBView in the process.
+ * @property {nsIMessenger} messenger Used to preserve "messenger" global value.
+ * The messenger object is the keeper of the 'undo' state and navigation
+ * history, which is why we do this.
+ *
+ * @property {boolean} folderPaneCollapsed In "folder" mode, has the user
+ * intentionally collapsed the folder pane.
+ * @property {boolean} messagePaneCollapsed In "folder" or "glodaSearch" mode,
+ * has the user intentionally collapsed the message pane.
+ *
+ * @property {nsIMsgDBHdr} hdr In "message" mode, the header of the message
+ * being displayed.
+ * @property {nsIMsgSearchSession} searchSession Used to preserve gSearchSession
+ * global value.
+ *
*/
let mailTabType = {
name: "mail",
panelId: "mailContent",
modes: {
+ /**
+ * The folder view displays the contents of an nsIMsgDBFolder, with the
+ * folder pane (potentially), thread pane (always), and message pane
+ * (potentially) displayed.
+ *
+ * The actual nsMsgDBView can be any of the following types of things:
+ * - A single folder.
+ * - A quicksearch on a single folder.
+ * - A virtual folder potentially containing messages from multiple
+ * folders. (eShowVirtualFolderResults)
+ */
folder: {
isDefault: true,
type: "folder",
- openTab: function(aTab, aFolderUri) {
- aTab.uriToOpen = aFolderUri;
-
- this.openTab(aTab); // call superclass logic
+ /// The set of panes that are legal to be displayed in this mode
+ legalPanes: {
+ folder: true,
+ thread: true,
+ message: true
+ },
+ openFirstTab: function(aTab) {
+ this.openTab(aTab, true, new MessagePaneDisplayWidget());
+ aTab.folderDisplay.makeActive();
},
- showTab: function(aTab) {
- this.folderAndThreadPaneVisible = true;
- ClearMessagePane();
-
- this.showTab(aTab);
+ /**
+ * @param aFolder The nsIMsgFolder to display.
+ * @param aMsgHdr Optional message header to display.
+ */
+ openTab: function(aTab, aFolder, aMsgHdr) {
+ // Get a tab that we can initialize our user preferences from.
+ // (We don't want to assume that our immediate predecessor was a
+ // "folder" tab.)
+ let modelTab = document.getElementById("tabmail")
+ .getTabInfoForCurrentOrFirstModeInstance(aTab.mode);
+ // copy its state
+ aTab.folderPaneCollapsed = modelTab.folderPaneCollapsed;
+ aTab.messagePaneCollapsed = modelTab.messagePaneCollapsed;
+
+ this.openTab(aTab, false, new MessagePaneDisplayWidget());
+
+ // Clear selection, because context clicking on a folder and opening in a
+ // new tab needs to have SelectFolder think the selection has changed.
+ // We also need to clear these globals to subvert the code that prevents
+ // folder loads when things haven't changed.
+ var folderTree = document.getElementById("folderTree");
+ folderTree.view.selection.clearSelection();
+ folderTree.view.selection.currentIndex = -1;
+
+ aTab.folderDisplay.makeActive();
+
+ // selecting the folder effectively calls
+ // aTab.folderDisplay.show(aFolder)
+ gFolderTreeView.selectFolder(aFolder);
+ if(aMsgHdr)
+ aTab.folderDisplay.selectMessage(aMsgHdr);
},
onTitleChanged: function(aTab, aTabNode) {
- if (!gMsgFolderSelected) {
+ if (!aTab.folderDisplay || !aTab.folderDisplay.displayedFolder) {
// Don't show "undefined" as title when there is no account.
aTab.title = " ";
return;
}
- aTab.title = gMsgFolderSelected.prettyName;
- if (!gMsgFolderSelected.isServer && this._getNumberOfRealAccounts() > 1)
- aTab.title += " - " + gMsgFolderSelected.server.prettyName;
-
- // The user may have changed folders, triggering our onTitleChanged callback.
+ // The user may have changed folders, triggering our onTitleChanged
+ // callback.
+ let folder = aTab.folderDisplay.displayedFolder;
+ aTab.title = folder.prettyName;
+ if (!folder.isServer && this._getNumberOfRealAccounts() > 1)
+ aTab.title += " - " + folder.server.prettyName;
+
// Update the appropriate attributes on the tab.
- aTabNode.setAttribute('SpecialFolder', getSpecialFolderString(gMsgFolderSelected));
- aTabNode.setAttribute('ServerType', gMsgFolderSelected.server.type);
- aTabNode.setAttribute('IsServer', gMsgFolderSelected.isServer);
- aTabNode.setAttribute('IsSecure', gMsgFolderSelected.server.isSecure);
+ aTabNode.setAttribute('SpecialFolder',
+ getSpecialFolderString(folder));
+ aTabNode.setAttribute('ServerType', folder.server.type);
+ aTabNode.setAttribute('IsServer', folder.isServer);
+ aTabNode.setAttribute('IsSecure', folder.server.isSecure);
}
},
+ /**
+ * The message view displays a single message. In this view, the folder
+ * pane and thread pane are forced hidden and only the message pane is
+ * displayed.
+ */
message: {
type: "message",
- openTab: function(aTab, aFolderUri, aMsgHdr) {
- aTab.uriToOpen = aFolderUri;
- aTab.hdr = aMsgHdr;
-
+ /// The set of panes that are legal to be displayed in this mode
+ legalPanes: {
+ folder: false,
+ thread: false,
+ message: true
+ },
+ openTab: function(aTab, aMsgHdr, aViewWrapperToClone) {
+ aTab.mode.onTitleChanged.call(this, aTab, null, aMsgHdr);
+
+ this.openTab(aTab, false, new MessageTabDisplayWidget());
+
+ if (aViewWrapperToClone)
+ aTab.folderDisplay.cloneView(aViewWrapperToClone);
+ else
+ aTab.folderDisplay.show(aMsgHdr.folder);
+
+ aTab.folderDisplay.selectMessage(aMsgHdr);
+
+ // we only want to make it active after setting up the view and the message
+ // to avoid generating bogus summarization events.
+ aTab.folderDisplay.makeActive();
+ },
+ onTitleChanged: function(aTab, aTabNode, aMsgHdr) {
+ if (aMsgHdr == null)
+ aMsgHdr = aTab.folderDisplay.selectedMessage;
aTab.title = "";
- if (aTab.hdr.flags & Components.interfaces.nsMsgMessageFlags.HasRe)
+ if (aMsgHdr.flags & Components.interfaces.nsMsgMessageFlags.HasRe)
aTab.title = "Re: ";
- if (aTab.hdr.mime2DecodedSubject)
- aTab.title += aTab.hdr.mime2DecodedSubject;
-
- aTab.title += " - " + aTab.hdr.folder.prettyName;
+ if (aMsgHdr.mime2DecodedSubject)
+ aTab.title += aMsgHdr.mime2DecodedSubject;
+
+ aTab.title += " - " + aMsgHdr.folder.prettyName;
if (this._getNumberOfRealAccounts() > 1)
- aTab.title += " - " + aTab.hdr.folder.server.prettyName;
-
- // let's try hiding the thread pane and folder pane
- this.folderAndThreadPaneVisible = false;
-
- this.openTab(aTab); // call superclass logic
-
- gCurrentlyDisplayedMessage = nsMsgViewIndex_None;
- ClearThreadPaneSelection();
- setTimeout(gDBView.selectFolderMsgByKey, 0, aTab.hdr.folder,
- aTab.hdr.messageKey);
- },
- showTab: function(aTab) {
- this.folderAndThreadPaneVisible = false;
- this.showTab(aTab);
+ aTab.title += " - " + aMsgHdr.folder.server.prettyName;
}
}
},
_getNumberOfRealAccounts : function() {
let mgr = Components.classes["@mozilla.org/messenger/account-manager;1"]
.getService(Components.interfaces.nsIMsgAccountManager);
let accountCount = mgr.accounts.Count();
// If we have an account, we also always have a "Local Folders" account.
return accountCount > 0 ? (accountCount - 1) : 0;
},
/**
- * Create the new tab's state, which engenders some side effects. Part of our
- * contract is that we leave the tab in the selected state.
+ * Common tab opening code shared by the various tab modes.
*/
- openTab: function(aTab) {
+ openTab: function(aTab, aIsFirstTab, aMessageDisplay) {
// Set the messagepane as the primary browser for content.
document.getElementById("messagepane").setAttribute("type",
"content-primary");
- ClearThreadPaneSelection();
-
- // Each tab gets its own messenger instance; I assume this is so each one
- // gets its own undo/redo stack?
- messenger = Components.classes["@mozilla.org/messenger;1"]
- .createInstance(Components.interfaces.nsIMessenger);
- messenger.setWindow(window, msgWindow);
- aTab.messenger = messenger;
-
- aTab.msgSelectedFolder = gMsgFolderSelected;
-
- // Clear selection, because context clicking on a folder and opening in a
- // new tab needs to have SelectFolder think the selection has changed.
- // We also need to clear these globals to subvert the code that prevents
- // folder loads when things haven't changed.
- var folderTree = document.getElementById("folderTree");
- folderTree.view.selection.clearSelection();
- folderTree.view.selection.currentIndex = -1;
- gMsgFolderSelected = null;
- msgWindow.openFolder = null;
-
- // Clear thread pane selection - otherwise, the tree tries to impose the
- // the current selection on the new view.
- gDBView = null; // clear gDBView so we won't try to close it.
- gFolderTreeView.selectFolder(GetMsgFolderFromUri(aTab.uriToOpen));
- aTab.dbView = gDBView;
+ aTab.messageDisplay = aMessageDisplay;
+ aTab.folderDisplay = new FolderDisplayWidget(aTab, aTab.messageDisplay);
+ aTab.folderDisplay.msgWindow = msgWindow;
+ aTab.folderDisplay.tree = document.getElementById("threadTree");
+ aTab.folderDisplay.treeBox = aTab.folderDisplay.tree.boxObject.QueryInterface(
+ Components.interfaces.nsITreeBoxObject);
+
+ if (aIsFirstTab) {
+ aTab.folderDisplay.messenger = messenger;
+ }
+ else {
+ // Each tab gets its own messenger instance; this provides each tab with
+ // its own undo/redo stack and back/forward navigation history.
+ messenger = Components.classes["@mozilla.org/messenger;1"]
+ .createInstance(Components.interfaces.nsIMessenger);
+ messenger.setWindow(window, msgWindow);
+ aTab.folderDisplay.messenger = messenger;
+ }
},
closeTab: function(aTab) {
- if (aTab.dbView)
- aTab.dbView.close();
- if (aTab.messenger)
- aTab.messenger.setWindow(null, null);
+ aTab.folderDisplay.close();
},
saveTabState: function(aTab) {
- aTab.messenger = messenger;
- aTab.dbView = gDBView;
- aTab.searchSession = gSearchSession;
- aTab.msgSelectedFolder = gMsgFolderSelected;
- if (!gDBView)
- return;
-
- aTab.firstVisibleRow = document.getElementById("threadTree").treeBoxObject.getFirstVisibleRow();
-
- if (gDBView.currentlyDisplayedMessage != nsMsgViewIndex_None)
- {
- try // there may not be a selected message.
- {
- var curMsgHdr = gDBView.hdrForFirstSelectedMessage;
- aTab.selectedMsgId = curMsgHdr.messageId;
- }
- catch (ex) {}
- }
- else
- {
- aTab.selectedMsgId = null;
- aTab.msgSelectedFolder = gDBView.msgFolder;
- }
- if (aTab.msgSelectedFolder)
- aTab.mailView = GetMailViewForFolder(aTab.msgSelectedFolder);
-
// Now let other tabs have a primary browser if they want.
document.getElementById("messagepane").setAttribute("type",
"content-targetable");
+
+ aTab.folderDisplay.makeInactive();
},
- _displayFolderAndThreadPane: function(show) {
- let collapse = !show;
+ /**
+ * Some panes simply are illegal in certain views, and some panes are legal
+ * but the user may have collapsed/hidden them. If that was not enough, we
+ * have three different layouts that are possible, each of which requires a
+ * slightly different DOM configuration, and accordingly for us to poke at
+ * different DOM nodes. Things are made somewhat simpler by our decision
+ * that all tabs share the same layout.
+ * This method takes the legal states and current display states and attempts
+ * to apply the appropriate logic to make it all work out. This method is
+ * not in charge of figuring out or preserving display states.
+ *
+ * We take a dictionary of desired visibility booleans as our argument because
+ * it is both readable and scalable.
+ *
+ * @param aLegalStates A dictionary where each key and value indicates whether
+ * the pane in question (key) is legal to be displayed in this mode. If
+ * the value is true, then the pane is legal. Omitted pane keys imply
+ * that the pane is illegal. Keys are:
+ * - folder: The folder (tree) pane.
+ * - thread: The thread pane.
+ * - message: The message pane. Required/assumed to be true for now.
+ * - glodaFacets: The gloda search facets pane.
+ * @param aVisibleStates A dictionary where each value indicates whether the
+ * pane should be 'visible' (not collapsed). Only panes that are governed
+ * by splitters are options here. Keys are:
+ * - folder: The folder (tree) pane.
+ * - message: The message pane.
+ */
+ _setPaneStates: function mailTabType_setPaneStates(aLegalStates,
+ aVisibleStates) {
let layout = pref.getIntPref("mail.pane_config.dynamic");
if (layout == kWidePaneConfig)
{
- document.getElementById("messengerBox").collapsed = collapse;
- // If opening a standalone message, need to give the messagepanebox flex.
- if (collapse)
+ // in the "wide" configuration, the #messengerBox is left holding the
+ // folder pane and thread pane, and the message pane has migrated to be
+ // its sibling (under #mailContent).
+ // Accordingly, if both the folder and thread panes are illegal, we
+ // want to collapse the #messengerBox and make sure the #messagepanebox
+ // fills up the screen. (For example, when in "message" mode.)
+ let collapseMessengerBox = !aLegalStates.folder && !aLegalStates.thread;
+ document.getElementById("messengerBox").collapsed = collapseMessengerBox;
+ if (collapseMessengerBox)
document.getElementById("messagepanebox").flex = 1;
}
- if (layout == kVerticalPaneConfig)
- document.getElementById("threadTree").collapsed = collapse;
- else
- document.getElementById("displayDeck").collapsed = collapse;
-
- let fpSplitter = document.getElementById("folderpane_splitter");
- if (show && fpSplitter.getAttribute("state") == "collapsed")
- {
- // If we're showing everything but the folder pane splitter was
- // previously collapsed, then keep it collapsed.
- // However, in the case of it being collapsed from showing a
- // a full message in a tab, then uncollapse just the splitter.
- if (fpSplitter.getAttribute("substate"))
- fpSplitter.collapsed = false;
- }
+ // -- folder pane
+ // collapse the splitter when not legal
+ document.getElementById("folderpane_splitter").collapsed =
+ !aLegalStates.folder;
+ // collapse the folder pane when not visible
+ document.getElementById("folderPaneBox").collapsed =
+ !aLegalStates.folder || !aVisibleStates.folder;
+
+ // -- thread pane
+ // in a vertical view, the threadContentArea sits in the #threadPaneBox
+ // next to the message pane and its splitter.
+ if (layout == kVerticalMailLayout)
+ document.getElementById("threadContentArea").collapsed =
+ !aLegalStates.thread;
+ // whereas in the default view, the displayDeck is the one next to the
+ // message pane and its splitter
else
- {
- fpSplitter.collapsed = collapse;
- document.getElementById("folderPaneBox").collapsed = collapse;
- }
-
- let tpSplitter = document.getElementById("threadpane-splitter");
- if (show && tpSplitter.getAttribute("state") == "collapsed")
- {
- // If we're showing everything but the thread pane splitter was
- // previously collapsed, then keep it collapsed.
- // However, in the case of it being collapsed from showing a
- // a full message in a tab, then uncollapse just the splitter.
- tpSplitter.collapsed = tpSplitter.getAttribute("substate") ? false : true;
- document.getElementById("messagepanebox").collapsed = true;
- }
- else
- {
- tpSplitter.collapsed = collapse;
- document.getElementById("messagepanebox").collapsed = false;
- }
- // Need to call ChangeMessagePaneVisibility() a little later after the
- // message was selected so that the tab state data gets the message key
- // saved off, which happens below in showTab().
- setTimeout(ChangeMessagePaneVisibility, 0, IsMessagePaneCollapsed());
-
+ document.getElementById("displayDeck").collapsed =
+ !aLegalStates.thread;
+
+ // the threadpane-splitter collapses the message pane (arguably a misnomer),
+ // but it only needs to exist when the thread-pane is legal
+ document.getElementById("threadpane-splitter").collapsed =
+ !aLegalStates.thread;
+
+ // Some things do not make sense if the thread pane is not legal.
+ // (This is likely an example of something that should be using the command
+ // mechanism to update the UI elements as to the state of what the user
+ // is looking at, rather than home-brewing it in here.)
try {
- document.getElementById("search-container").collapsed = collapse;
+ // you can't quick-search if you don't have a collection of messages
+ document.getElementById("search-container").collapsed =
+ !aLegalStates.thread;
} catch (ex) {}
try {
- document.getElementById("mailviews-container").collapsed = collapse;
+ // views only work on the thread pane; no thread pane, no views
+ document.getElementById("mailviews-container").collapsed =
+ !aLegalStates.thread;
} catch (ex) {}
- },
-
- _folderAndThreadPaneVisible: true,
- get folderAndThreadPaneVisible() { return this._folderAndThreadPaneVisible; },
- set folderAndThreadPaneVisible(aDesiredVisible) {
- if (aDesiredVisible != this._folderAndThreadPaneVisible) {
- this._displayFolderAndThreadPane(aDesiredVisible);
- this._folderAndThreadPaneVisible = aDesiredVisible;
- }
+
+ // -- message pane
+ // the message pane can only be collapsed when the thread pane is legal
+ document.getElementById("messagepanebox").collapsed =
+ aLegalStates.thread && !aVisibleStates.message;
+
+ // -- gloda facets
+ //document.getElementById("glodaSearchFacets").hidden =
+ // !aLegalStates.glodaFacets;
},
showTab: function(aTab) {
// Set the messagepane as the primary browser for content.
document.getElementById("messagepane").setAttribute("type",
"content-primary");
- // restore globals
- messenger = aTab.messenger;
- gDBView = aTab.dbView;
- gSearchSession = aTab.searchSession;
-
- // restore selection in folder pane;
- let folderToSelect = gDBView ? gDBView.msgFolder : aTab.msgSelectedFolder;
- // restore view state if we had one
- var row = gFolderTreeView.getIndexOfFolder(folderToSelect);
-
- var treeBoxObj = document.getElementById("folderTree").treeBoxObject;
- var folderTreeSelection = treeBoxObj.view.selection;
- // make sure that row.value is valid so that it doesn't mess up
- // the call to ensureRowIsVisible().
- if ((row >= 0) && !folderTreeSelection.isSelected(row))
- {
- gMsgFolderSelected = folderToSelect;
- folderTreeSelection.selectEventsSuppressed = true;
- folderTreeSelection.select(row);
- treeBoxObj.ensureRowIsVisible(row);
- folderTreeSelection.selectEventsSuppressed = false;
- }
- if (gDBView)
- {
- UpdateColumnsForView(gMsgFolderSelected, gDBView.viewType);
- UpdateSortIndicators(gDBView.sortType, gDBView.sortOrder);
- // This sets the thread pane tree's view to the gDBView view.
- RerootThreadPane();
- // Only refresh the view picker if the views toolbar is visible.
- if (document.getElementById("mailviews-container"))
- UpdateViewPickerByValue(aTab.mailView);
-
- // We need to restore the selection to what it was when we switched away
- // from this tab. We need to remember the selected keys, instead of the
- // selected indices, since the view might have changed. But maybe the
- // selectedIndices adjust as items are added/removed from the (hidden)
- // view.
- try
- {
- if (aTab.selectedMsgId && aTab.msgSelectedFolder)
- {
- // We clear the selection in order to generate an event when we
- // re-select our message.
- ClearThreadPaneSelection();
-
- var msgDB = aTab.msgSelectedFolder.msgDatabase;
- var msgHdr = msgDB.getMsgHdrForMessageID(aTab.selectedMsgId);
- setTimeout(gDBView.selectFolderMsgByKey, 0, aTab.msgSelectedFolder,
- msgHdr.messageKey);
- }
- // We do not clear the selection if there was more than one message
- // displayed. this leaves our selection intact. there was originally
- // some claim that the selection might lose synchronization with the
- // view, but this is unsubstantiated. said comment came from the
- // original code that stored information on the selected rows, but
- // then failed to do anything with it, probably because there is no
- // existing API call that accomplishes it.
+ aTab.folderDisplay.makeActive();
+
+ // - restore folder pane/tree selection
+ if (aTab.folderDisplay.displayedFolder) {
+ // but don't generate any events while doing so!
+ gFolderTreeView.selection.selectEventsSuppressed = true;
+ try {
+ gFolderTreeView.selectFolder(aTab.folderDisplay.displayedFolder);
}
- catch (ex) {dump(ex);}
-
- document.getElementById("threadTree").treeBoxObject.scrollToRow(aTab.firstVisibleRow);
- ShowThreadPane();
- }
- else if (gMsgFolderSelected.isServer)
- {
- UpdateStatusQuota(null);
- // Load AccountCentral page here.
- ShowAccountCentral();
+ finally {
+ gIgnoreSyntheticFolderPaneSelectionChange = true;
+ gFolderTreeView.selection.selectEventsSuppressed = false;
+ }
}
},
supportsCommand: function(aTab, aCommand) {
return DefaultController.supportsCommand(aCommand);
},
isCommandEnabled: function(aTab, aCommand) {
@@ -1912,90 +1971,64 @@ let mailTabType = {
function MsgOpenNewWindowForFolder(folderURI, msgKeyToSelect)
{
if (folderURI) {
window.openDialog("chrome://messenger/content/", "_blank",
"chrome,all,dialog=no", folderURI, msgKeyToSelect);
return;
}
- // Use gFolderTreeView.getSelectedFolders() to find out which folder to open
- // instead of GetLoadedMsgFolder().URI. This is required because on a
- // right-click, the currentIndex value will be different from the actual row
- // that is highlighted. gFolderTreeView.getSelectedFolders() will return the
- // folder that is highlighted.
+ // If there is a right-click happening, gFolderTreeView.getSelectedFolders()
+ // will tell us about it (while the selection's currentIndex would reflect
+ // the node that was selected/displayed before the right-click.)
let selectedFolders = gFolderTreeView.getSelectedFolders();
for (let i = 0; i < selectedFolders.length; i++) {
window.openDialog("chrome://messenger/content/", "_blank",
"chrome,all,dialog=no",
selectedFolders[i].URI, msgKeyToSelect);
}
}
-function MsgOpenNewTabForFolder(folderURI, msgKeyToSelect)
+/**
+ * UI-triggered command to open the currently selected folder(s) in new tabs.
+ */
+function MsgOpenNewTabForFolder()
{
- // XXX implement the usage of msgKeyToSelect...
- if (folderURI) {
- document.getElementById("tabmail").openTab("folder", folderURI);
- return;
- }
-
- // Use gFolderTreeView.getSelectedFolders() to find out which folder to open
- // instead of GetLoadedMsgFolder().URI. This is required because on a
- // right-click, the currentIndex value will be different from the actual row
- // that is highlighted. gFolderTreeView.getSelectedFolders() will return the
- // folder that is highlighted.
+ // If there is a right-click happening, gFolderTreeView.getSelectedFolders()
+ // will tell us about it (while the selection's currentIndex would reflect
+ // the node that was selected/displayed before the right-click.)
let selectedFolders = gFolderTreeView.getSelectedFolders();
for (let i = 0; i < selectedFolders.length; i++) {
- // Set up the first tab, which was previously invisible.
- // This assumes the first tab is always a 3-pane ui, which
- // may not be right, especially if we have the ability
- // to persist your tab setup.
- document.getElementById("tabmail").openTab("folder", selectedFolders[i].URI);
+ document.getElementById("tabmail").openTab("folder", selectedFolders[i]);
}
}
-function MsgOpenNewTabForMessage(messageKey, folderUri)
+/**
+ * UI-triggered command to open the currently selected message in a new tab.
+ */
+function MsgOpenNewTabForMessage()
{
- var hdr;
-
- // messageKey can be 0 for an actual message (first message in the folder)
- if (folderUri)
- {
- hdr = GetMsgFolderFromUri(folderUri).GetMessageHeader(messageKey);
- }
- else
- {
- hdr = gDBView.hdrForFirstSelectedMessage;
- // Use the header's folder - this will open a msg in a virtual folder view
- // in its real folder, which is needed if the msg wouldn't be in a new
- // view with the same terms - e.g., it's read and the view is unread only.
- // If we cloned the view, we wouldn't have to do this.
- folderUri = hdr.folder.URI;
- }
-
- // Fix it so we won't try to load the previously loaded message.
- hdr.folder.lastMessageLoaded = nsMsgKey_None;
-
- document.getElementById('tabmail').openTab("message", folderUri, hdr);
+ if (!gFolderDisplay.selectedMessage)
+ return;
+ document.getElementById('tabmail').openTab("message",
+ gFolderDisplay.selectedMessage,
+ gFolderDisplay.view);
}
function MsgOpenSelectedMessages()
{
// Toggle message body (rss summary) and content-base url in message
// pane per pref, otherwise open summary or web page in new window.
- if (IsFeedItem() && GetFeedOpenHandler() == 2) {
+ if (gFolderDisplay.selectedMessageIsFeed && GetFeedOpenHandler() == 2) {
FeedSetContentViewToggle();
return;
}
- var dbView = GetDBView();
-
- var indices = GetSelectedIndices(dbView);
- var numMessages = indices.length;
+ let selectedMessages = gFolderDisplay.selectedMessages;
+ var numMessages = selectedMessages.length;
var windowReuse = gPrefBranch.getBoolPref("mailnews.reuse_message_window");
// This is a radio type button pref, currently with only 2 buttons.
// We need to keep the pref type as 'bool' for backwards compatibility
// with 4.x migrated prefs. For future radio button(s), please use another
// pref (either 'bool' or 'int' type) to describe it.
//
// windowReuse values: false, true
@@ -2013,55 +2046,37 @@ function MsgOpenSelectedMessages()
var text = gMessengerBundle.getFormattedString("openWindowWarningText", [numMessages]);
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
if (!promptService.confirm(window, title, text))
return;
}
for (var i = 0; i < numMessages; i++) {
- MsgOpenNewWindowForMessage(dbView.getURIForViewIndex(indices[i]),
- dbView.getFolderForViewIndex(indices[i]).URI);
+ MsgOpenNewWindowForMessage(selectedMessages[i]);
}
}
function MsgOpenSelectedMessageInExistingWindow()
{
var windowID = GetWindowByWindowType("mail:messageWindow");
if (!windowID)
return false;
- try {
- var messageURI = gDBView.URIForFirstSelectedMessage;
- var msgHdr = gDBView.hdrForFirstSelectedMessage;
-
- // Reset the window's message uri and folder uri vars, and
- // update the command handlers to what's going to be used.
- // This has to be done before the call to CreateView().
- windowID.gCurrentMessageUri = messageURI;
- windowID.gCurrentFolderUri = msgHdr.folder.URI;
- windowID.UpdateMailToolbar('MsgOpenExistingWindowForMessage');
-
- // Even if the folder uri's match, we can't use the existing view
- // (msgHdr.folder.URI == windowID.gCurrentFolderUri)
- // - the reason is quick search and mail views. See bug #187673.
- //
- // For the sake of simplicity, let's always call CreateView(gDBView);
- // which will clone gDBView.
- windowID.CreateView(gDBView);
- windowID.LoadMessageByMsgKey(msgHdr.messageKey);
-
- // bring existing window to front
- windowID.focus();
- return true;
- }
- catch (ex) {
- dump("reusing existing standalone message window failed: " + ex + "\n");
- }
- return false;
+ var msgHdr = gFolderDisplay.selectedMessage;
+
+ // (future work: perhaps make the window have a method we can call to do this)
+ // make the window's folder clone our view
+ windowID.gFolderDisplay.cloneView(gFolderDisplay.view);
+ // boss the window into showing our message
+ windowID.gFolderDisplay.selectMessage(msgHdr);
+
+ // bring existing window to front
+ windowID.focus();
+ return true;
}
function MsgOpenFromFile()
{
const nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
@@ -2086,106 +2101,92 @@ function MsgOpenFromFile()
dump("filePicker.chooseInputFile threw an exception\n");
return;
}
var uri = fp.fileURL.QueryInterface(Components.interfaces.nsIURL);
uri.query = "type=application/x-message-display";
window.openDialog("chrome://messenger/content/messageWindow.xul", "_blank",
- "all,chrome,dialog=no,status,toolbar", uri, null, null);
+ "all,chrome,dialog=no,status,toolbar", uri);
}
-function MsgOpenNewWindowForMessage(messageUri, folderUri)
+function MsgOpenNewWindowForMessage(aMsgHdr)
{
- if (!messageUri)
- // Use GetFirstSelectedMessage() to find out which message to open
- // instead of gDBView.getURIForViewIndex(currentIndex). This is
- // required because on a right-click, the currentIndex value will be
- // different from the actual row that is highlighted.
- // GetFirstSelectedMessage() will return the message that is
- // highlighted.
- messageUri = GetFirstSelectedMessage();
-
- if (!folderUri)
- // Use GetSelectedMsgFolders() to find out which message to open
- // instead of gDBView.getURIForViewIndex(currentIndex). This is
- // required because on a right-click, the currentIndex value will be
- // different from the actual row that is highlighted.
- // GetSelectedMsgFolders() will return the message that is
- // highlighted.
- folderUri = GetSelectedMsgFolders()[0].URI;
-
- // be sure to pass in the current view....
- if (messageUri && folderUri) {
+ // no message header provided? get the selected message (this will give us
+ // the right-click selected message if that's what is going down.)
+ if (!aMsgHdr)
+ aMsgHdr = gFolderDisplay.selectedMessage;
+
+ // (there might not have been a selected message, so check...)
+ if (aMsgHdr)
+ // we also need to tell the window about our current view so that it can
+ // clone it. This enables advancing through the messages, etc.
window.openDialog("chrome://messenger/content/messageWindow.xul", "_blank",
"all,chrome,dialog=no,status,toolbar",
- messageUri, folderUri, gDBView);
- }
+ aMsgHdr, gFolderDisplay.view);
}
function MsgJunk()
{
MsgJunkMailInfo(true);
JunkSelectedMessages(!SelectedMessagesAreJunk());
}
function UpdateJunkButton()
{
- let hdr = msgHdrForCurrentMessage();
- if (!hdr) // .eml file
+ // The junk message should slave off the selected message, as the preview pane
+ // may not be visible
+ let hdr = gFolderDisplay.selectedMessage;
+ // But only the message display knows if we are dealing with a dummy.
+ if (gMessageDisplay.isDummy) // .eml file
return;
let junkScore = hdr.getStringProperty("junkscore");
let hideJunk = (junkScore != "") && (junkScore != "0");
- if (isNewsURI(hdr.folder.URI))
+ if (gFolderDisplay.selectedMessageIsNews)
hideJunk = true;
- // which DOM node is the current junk button in the
- // message reader depends on whether it's the collapsed or
- // expanded header
- let buttonBox = document.getElementById(gCollapsedHeaderViewMode ?
- "collapsedButtonBox" : "expandedButtonBox");
- buttonBox.getButton('hdrJunkButton').disabled = hideJunk;
+
+ getCurrentMsgHdrButtonBox().getButton('hdrJunkButton').disabled = hideJunk;
}
function MsgMarkMsgAsRead()
{
MarkSelectedMessagesRead(!SelectedMessagesAreRead());
}
function MsgMarkAsFlagged()
{
MarkSelectedMessagesFlagged(!SelectedMessagesAreFlagged());
}
function MsgMarkReadByDate()
{
window.openDialog("chrome://messenger/content/markByDate.xul","",
"chrome,modal,titlebar,centerscreen",
- GetLoadedMsgFolder());
+ gFolderDisplay.displayedFolder);
}
function MsgMarkAllRead()
{
- var folder = GetSelectedMsgFolders()[0];
-
- if (folder)
- folder.markAllMessagesRead(msgWindow);
+ let folders = gFolderTreeView.getSelectedFolders();
+ for (let i = 0; i < folders.length; i++)
+ folders[i].markAllMessagesRead(msgWindow);
}
function MsgFilters(emailAddress, folder)
{
if (!folder)
{
// Try to determine the folder from the selected message.
if (gDBView)
{
try
{
- var msgHdr = gDBView.hdrForFirstSelectedMessage;
+ var msgHdr = gFolderDisplay.selectedMessage;
var accountKey = msgHdr.accountKey;
if (accountKey.length > 0)
{
var account = accountManager.getAccount(accountKey);
if (account)
{
var server = account.incomingServer;
if (server)
@@ -2209,17 +2210,17 @@ function MsgFilters(emailAddress, folder
}
}
}
var args;
if (emailAddress)
{
// We have to do prefill filter so we are going to launch the
// filterEditor dialog and prefill that with the emailAddress.
- args = { filterList: folder.getFilterList(msgWindow) };
+ args = { filterList: folder.getEditableFilterList(msgWindow) };
args.filterName = emailAddress;
window.openDialog("chrome://messenger/content/FilterEditor.xul", "",
"chrome, modal, resizable,centerscreen,dialog=yes", args);
// If the user hits ok in the filterEditor dialog we set args.refresh=true
// there we check this here in args to show filterList dialog.
if ("refresh" in args && args.refresh)
{
@@ -2267,43 +2268,30 @@ function MsgApplyFilters()
newFilterIndex++;
}
}
filterService.applyFiltersToFolders(tempFilterList, selectedFolders, msgWindow);
}
function MsgApplyFiltersToSelection()
{
- var filterService = Components.classes["@mozilla.org/messenger/services/filters;1"]
- .getService(Components.interfaces.nsIMsgFilterService);
-
- var folder = gDBView.msgFolder;
- var indices = GetSelectedIndices(gDBView);
- if (indices && indices.length)
- {
- var selectedMsgs = Components.classes["@mozilla.org/array;1"]
- .createInstance(Components.interfaces.nsIMutableArray);
- for (var i = 0; i < indices.length; i++)
- {
- try
- {
- // Getting the URI will tell us if the item is real or a dummy header
- var uri = gDBView.getURIForViewIndex(indices[i]);
- if (uri)
- {
- var msgHdr = folder.GetMessageHeader(gDBView.getKeyAt(indices[i]));
- if (msgHdr)
- selectedMsgs.appendElement(msgHdr, false);
- }
- } catch (ex) {}
- }
+ // bail if we're dealing with a dummy header
+ if (gMessageDisplay.isDummy)
+ return;
+
+ var selectedMessages = gFolderDisplay.selectedMessages;
+ if (selectedMessages.length) {
+ var filterService =
+ Components.classes["@mozilla.org/messenger/services/filters;1"]
+ .getService(Components.interfaces.nsIMsgFilterService);
filterService.applyFilters(Components.interfaces.nsMsgFilterType.Manual,
- selectedMsgs,
- folder,
+ toXPCOMArray(selectedMessages,
+ Components.interfaces.nsIMutableArray),
+ gFolderDisplay.displayedFolder,
msgWindow);
}
}
function ChangeMailLayout(newLayout)
{
gPrefBranch.setIntPref("mail.pane_config.dynamic", newLayout);
}
@@ -2381,18 +2369,18 @@ function ToggleInlineAttachment(target)
var viewAttachmentInline = !pref.getBoolPref("mail.inline_attachments");
pref.setBoolPref("mail.inline_attachments", viewAttachmentInline)
target.setAttribute("checked", viewAttachmentInline ? "true" : "false");
ReloadMessage();
}
function PrintEnginePrintInternal(doPrintPreview, msgType)
{
- var messageList = GetSelectedMessages();
- if (messageList.length == 0) {
+ var messageList = gFolderDisplay.selectedMessageUris;
+ if (!messageList) {
dump("PrintEnginePrintInternal(): No messages selected.\n");
return;
}
window.openDialog("chrome://messenger/content/msgPrintEngine.xul", "",
"chrome,dialog=no,all,centerscreen",
messageList.length, messageList, statusFeedback,
doPrintPreview, msgType, window);
@@ -2670,17 +2658,17 @@ function CommandUpdate_UndoRedo()
EnableMenuItem("menu_undo", SetupUndoRedoCommand("cmd_undo"));
EnableMenuItem("menu_redo", SetupUndoRedoCommand("cmd_redo"));
}
function SetupUndoRedoCommand(command)
{
// If we have selected a server, and are viewing account central
// there is no loaded folder.
- var loadedFolder = GetLoadedMsgFolder();
+ var loadedFolder = gFolderDisplay.displayedFolder;
if (!loadedFolder || !loadedFolder.server.canUndoDeleteOnServer)
return false;
var canUndoOrRedo;
var txnType;
if (command == "cmd_undo")
{
canUndoOrRedo = messenger.canUndo();
@@ -2689,41 +2677,57 @@ function SetupUndoRedoCommand(command)
else
{
canUndoOrRedo = messenger.canRedo();
txnType = messenger.getRedoTransactionType();
}
if (canUndoOrRedo)
{
- var commands =
+ var commands =
['valueDefault', 'valueDeleteMsg', 'valueMoveMsg', 'valueCopyMsg', 'valueUnmarkAllMsgs'];
goSetMenuValue(command, commands[txnType]);
}
else
{
goSetMenuValue(command, 'valueDefault');
}
return canUndoOrRedo;
}
+/**
+ * Triggered by the global JunkStatusChanged notification, we handle updating
+ * the message display if our displayed message might have had its junk status
+ * change. This primarily entails updating the notification bar (that thing
+ * that appears above the message and says "this message might be junk") and
+ * (potentially) reloading the message because junk status affects the form of
+ * HTML display used (sanitized vs not).
+ * When our tab implementation is no longer multiplexed (reusing the same
+ * display widget), this must be moved into the MessageDisplayWidget or
+ * otherwise be scoped to the tab.
+ */
function HandleJunkStatusChanged(folder)
{
+ // We have nothing to do (and should bail) if:
+ // - There is no currently displayed message.
+ // - The displayed message is an .eml file from disk or an attachment.
+ // - The folder that has had a junk change is not backing the display folder.
+
// This might be the stand alone window, open to a message that was
// and attachment (or on disk), in which case, we want to ignore it.
- var loadedMessage = GetLoadedMessage();
- if (!loadedMessage || (/type=application\/x-message-display/.test(loadedMessage)) ||
- !IsCurrentLoadedFolder(folder))
+ if (!gMessageDisplay.displayedMessage ||
+ gMessageDisplay.isDummy ||
+ gFolderDisplay.displayedFolder != folder)
return;
// If multiple message are selected and we change the junk status
// we don't want to show the junk bar (since the message pane is blank).
var msgHdr = null;
if (GetNumSelectedMessages() == 1)
- msgHdr = messenger.msgHdrFromURI(loadedMessage);
+ msgHdr = gMessageDisplay.displayedMessage;
var junkBarWasDisplayed = gMessageNotificationBar.isFlagSet(kMsgNotificationJunkBar);
gMessageNotificationBar.setJunkMsg(msgHdr);
// Only reload message if junk bar display state has changed.
if (msgHdr && junkBarWasDisplayed != gMessageNotificationBar.isFlagSet(kMsgNotificationJunkBar))
{
// We may be forcing junk mail to be rendered with sanitized html.
// In that scenario, we want to reload the message if the status has just
@@ -2764,17 +2768,17 @@ var gMessageNotificationBar =
mMsgNotificationBar: document.getElementById('msgNotificationBar'),
setJunkMsg: function(aMsgHdr)
{
var isJunk = false;
if (aMsgHdr)
{
- var junkScore = aMsgHdr.getStringProperty("junkscore");
+ var junkScore = aMsgHdr.getStringProperty("junkscore");
isJunk = ((junkScore != "") && (junkScore != "0"));
}
this.updateMsgNotificationBar(kMsgNotificationJunkBar, isJunk);
goUpdateCommand('button_junk');
},
@@ -2834,34 +2838,25 @@ function LoadMsgWithRemoteContent()
// we want to get the msg hdr for the currently selected message
// change the "remoteContentBar" property on it
// then reload the message
setMsgHdrPropertyAndReload("remoteContentPolicy", kAllowRemoteContent);
}
/**
- * Returns the msg hdr associated with the current loaded message.
- */
-function msgHdrForCurrentMessage()
-{
- var msgURI = GetLoadedMessage();
- return (msgURI && !(/type=application\/x-message-display/.test(msgURI))) ? messenger.msgHdrFromURI(msgURI) : null;
-}
-
-/**
* Reloads the message after adjusting the remote content policy for the sender.
- * Iterate through the local address books looking for a card with the same e-mail address as the
+ * Iterate through the local address books looking for a card with the same e-mail address as the
* sender of the current loaded message. If we find a card, update the allow remote content field.
* If we can't find a card, prompt the user with a new AB card dialog, pre-selecting the remote content field.
*/
function allowRemoteContentForSender()
{
// get the sender of the msg hdr
- var msgHdr = msgHdrForCurrentMessage();
+ var msgHdr = gMessageDisplay.displayedMessage;
if (!msgHdr)
return;
var headerParser = Components.classes["@mozilla.org/messenger/headerparser;1"]
.getService(Components.interfaces.nsIMsgHeaderParser);
var names = {};
var addresses = {};
var fullNames = {};
@@ -2925,17 +2920,17 @@ function IgnorePhishingWarning()
// This property is used to supress the phishing bar for the message.
setMsgHdrPropertyAndReload("notAPhishMessage", 1);
}
function setMsgHdrPropertyAndReload(aProperty, aValue)
{
// we want to get the msg hdr for the currently selected message
// change the appropiate property on it then reload the message
- var msgHdr = msgHdrForCurrentMessage();
+ var msgHdr = gMessageDisplay.displayedMessage;
if (msgHdr)
{
msgHdr.setUint32Property(aProperty, aValue);
ReloadMessage();
}
}
/**
@@ -2955,40 +2950,40 @@ function ClearPendingReadTimer()
{
if (gMarkViewedMessageAsReadTimer)
{
clearTimeout(gMarkViewedMessageAsReadTimer);
gMarkViewedMessageAsReadTimer = null;
}
}
-// this is called when layout is actually finished rendering a
+// this is called when layout is actually finished rendering a
// mail message. OnMsgLoaded is called when libmime is done parsing the message
function OnMsgParsed(aUrl)
{
// If rss feed (has 'content-base' header), show summary or load web
// page per pref; earliest we have content DOM is here (onMsgParsed).
FeedSetContentView();
// browser doesn't do this, but I thought it could be a useful thing to test out...
- // If the find bar is visible and we just loaded a new message, re-run
+ // If the find bar is visible and we just loaded a new message, re-run
// the find command. This means the new message will get highlighted and
// we'll scroll to the first word in the message that matches the find text.
var findBar = document.getElementById("FindToolbar");
if (!findBar.hidden)
findBar.onFindAgainCommand(false);
// Run the phishing detector on the message if it hasn't been marked as not
// a scam already.
- var msgHdr = msgHdrForCurrentMessage();
+ var msgHdr = gMessageDisplay.displayedMessage;
if (msgHdr && !msgHdr.getUint32Property("notAPhishMessage"))
gPhishingDetector.analyzeMsgForPhishingURLs(aUrl);
// notify anyone (e.g., extensions) who's interested in when a message is loaded.
- var msgURI = GetLoadedMessage();
+ var msgURI = gFolderDisplay.selectedMessageUris[0];
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.notifyObservers(msgWindow.msgHeaderSink, "MsgMsgDisplayed", msgURI);
// scale any overflowing images
var doc = document.getElementById("messagepane").contentDocument;
var imgs = doc.getElementsByTagName("img");
for each (var img in imgs)
@@ -3010,33 +3005,22 @@ function OnMsgLoaded(aUrl)
// nsIMsgMailNewsUrl.folder throws an error when opening .eml files.
var folder;
try {
folder = aUrl.folder;
}
catch (ex) {}
- var msgURI = GetLoadedMessage();
-
- if (!folder || !msgURI)
+ var msgHdr = gMessageDisplay.displayedMessage;
+ if (!folder || !msgHdr)
return;
- // If we are in the middle of a delete or move operation, make sure that
- // if the user clicks on another message then that message stays selected
- // and the selection does not "snap back" to the message chosen by
- // SetNextMessageAfterDelete() when the operation completes (bug 243532).
- // But the just loaded message might be getting deleted, if the user
- // deletes it before the message is loaded (bug 183394).
var wintype = document.documentElement.getAttribute('windowtype');
- if (wintype == "mail:messageWindow" ||
- GetThreadTree().view.selection.currentIndex != gSelectedIndexWhenDeleting)
- gNextMessageViewIndexAfterDelete = -2;
-
- var msgHdr = msgHdrForCurrentMessage();
+
gMessageNotificationBar.setJunkMsg(msgHdr);
goUpdateCommand('button_delete');
var markReadAutoMode = gPrefBranch.getBoolPref("mailnews.mark_message_read.auto");
// We just finished loading a message. If messages are to be marked as read
// automatically, set a timer to mark the message is read after n seconds
@@ -3062,17 +3046,17 @@ function OnMsgLoaded(aUrl)
{
MarkMessageAsRead(msgHdr);
}
}
// See if MDN was requested but has not been sent.
HandleMDNResponse(aUrl);
- if (!IsImapMessage(msgURI))
+ if (!gFolderDisplay.selectedMessageIsImap)
return;
var imapServer = folder.server.QueryInterface(Components.interfaces.nsIImapIncomingServer);
if (imapServer.storeReadMailInPFC)
{
// Look in read mail PFC for msg with same msg id - if we find one,
// don't put this message in the read mail pfc.
var outputPFC = imapServer.GetReadMailPFC(true);
@@ -3100,26 +3084,25 @@ function OnMsgLoaded(aUrl)
* no need to check it either.
*/
function HandleMDNResponse(aUrl)
{
if (!aUrl)
return;
var msgFolder = aUrl.folder;
- var msgURI = GetLoadedMessage();
- if (!msgFolder || !msgURI || IsNewsMessage(msgURI))
+ var msgHdr = gFolderDisplay.selectedMessage;
+ if (!msgFolder || !msgHdr || gFolderDisplay.selectedMessageIsNews)
return;
// if the message is marked as junk, do NOT attempt to process a return receipt
// in order to better protect the user
if (SelectedMessagesAreJunk())
return;
- var msgHdr = messenger.msgHdrFromURI(msgURI);
var mimeHdr;
try {
mimeHdr = aUrl.mimeHeaders;
} catch (ex) {
return;
}
@@ -3159,30 +3142,26 @@ function HandleMDNResponse(aUrl)
msgHdr.OrFlags(Components.interfaces.nsMsgMessageFlags.MDNReportSent);
// Commit db changes.
var msgdb = msgFolder.msgDatabase;
if (msgdb)
msgdb.Commit(ADDR_DB_LARGE_COMMIT);
}
-function QuickSearchFocus()
+function QuickSearchFocus()
{
var quickSearchTextBox = document.getElementById('searchInput');
if (quickSearchTextBox)
quickSearchTextBox.focus();
}
function MsgSearchMessages()
{
- var preselectedFolder = null;
- if ("GetFirstSelectedMsgFolder" in window)
- preselectedFolder = GetFirstSelectedMsgFolder();
-
- var args = { folder: preselectedFolder };
+ var args = { folder: gFolderDisplay.displayedFolder };
OpenOrFocusWindow(args, "mailnews:search", "chrome://messenger/content/SearchDialog.xul");
}
function MsgJunkMailInfo(aCheckFirstUse)
{
if (aCheckFirstUse) {
if (!pref.getBoolPref("mailnews.ui.junk.firstuse"))
return;
@@ -3206,17 +3185,17 @@ function MsgJunkMailInfo(aCheckFirstUse)
"centerscreen,resizeable=no,titlebar,chrome,modal", null);
}
function MsgSearchAddresses()
{
var args = { directory: null };
OpenOrFocusWindow(args, "mailnews:absearch", "chrome://messenger/content/ABSearchDialog.xul");
}
-
+
function MsgFilterList(args)
{
OpenOrFocusWindow(args, "mailnews:filterlist", "chrome://messenger/content/FilterListDialog.xul");
}
function GetWindowByWindowType(windowType)
{
var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
@@ -3244,17 +3223,17 @@ function FeedSetContentViewToggle()
gShowFeedSummaryToggle = true;
FeedSetContentView(gShowFeedSummary ? 0 : 1);
}
// Check message format
function FeedCheckContentFormat()
{
// Not an rss message
- if (!IsFeedItem())
+ if (!gFolderDisplay.selectedMessageIsFeed)
return false;
var contentWindowDoc = window.top.content.document;
// Thunderbird 2 rss messages with 'Show article summary' not selected,
// ie message body constructed to show web page in an iframe, can't show
// a summary - notify user.
var rssIframe = contentWindowDoc.getElementById('_mailrssiframe');
--- a/mail/base/content/mailWindowOverlay.xul
+++ b/mail/base/content/mailWindowOverlay.xul
@@ -57,16 +57,18 @@
%brandDTD;
]>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/x-javascript" src="chrome://messenger/content/mailCommands.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/junkCommands.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/mailWindowOverlay.js"/>
+<script type="application/x-javascript" src="chrome://messenger/content/messageDisplay.js"/>
+<script type="application/x-javascript" src="chrome://messenger/content/folderDisplay.js"/>
<script type="application/x-javascript" src="chrome://messenger-newsblog/content/newsblogOverlay.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/mail-offline.js"/>
<script type="application/x-javascript" src="chrome://global/content/printUtils.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/msgViewPickerOverlay.js"/>
<script type="application/x-javascript" src="chrome://global/content/viewZoomOverlay.js"/>
<stringbundleset id="stringbundleset">
<stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
@@ -722,17 +724,17 @@
oncommand="MsgGetMessage();"/>
<menuitem id="folderPaneContext-openNewWindow"
label="&folderContextOpenNewWindow.label;"
accesskey="&folderContextOpenNewWindow.accesskey;"
oncommand="MsgOpenNewWindowForFolder(null,-1);"/>
<menuitem id="folderPaneContext-openNewTab"
label="&folderContextOpenNewTab.label;"
accesskey="&folderContextOpenNewTab.accesskey;"
- oncommand="MsgOpenNewTabForFolder(null,-1);"/>
+ oncommand="MsgOpenNewTabForFolder();"/>
<menuitem id="folderPaneContext-searchMessages"
label="&folderContextSearchMessages.label;"
accesskey="&folderContextSearchMessages.accesskey;"
command="cmd_search"/>
<menuitem id="folderPaneContext-subscribe"
label="&folderContextSubscribe.label;"
accesskey="&folderContextSubscribe.accesskey;"
oncommand="MsgSubscribe();"/>
@@ -1635,16 +1637,17 @@
<menupopup id="printMenu" onpopupshowing="goUpdateCommand('cmd_printpreview');">
<menuitem id="button-printMenu"
label="&printCmd.label;"
accesskey="&printCmd.accesskey;"
default="true"/>
<menuitem id="button-printPreviewMenu"
label="&printPreviewCmd.label;"
accesskey="&printPreviewCmd.accesskey;"
+ observes="cmd_printpreview"
command="cmd_printpreview"/>
</menupopup>
</toolbarbutton>
#endif
<toolbarbutton id="button-mark"
type="menu-button"
class="toolbarbutton-1"
label="&markButton.label;"
new file mode 100644
--- /dev/null
+++ b/mail/base/content/messageDisplay.js
@@ -0,0 +1,468 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ * David Bienvenu <bienvenu@nventure.com>
+ * Siddharth Agarwal <sid.bugzilla@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * Base abstraction for message display along the line of FolderDisplayWidget,
+ * but for message display. This really only exists to keep
+ * FolderDisplayWidget manageable and free from taking on responsibility for
+ * the (different) horrors of message display. The reality of the situation
+ * is that FolderDisplayWidget still has a lot to do with message display,
+ * and so we are just where helper logic gets to live, but the FDW still
+ * may deal with some things internally.
+ * You should not use this class directly, but rather its friendly subclasses
+ * that live later in this file.
+ */
+function MessageDisplayWidget() {
+}
+MessageDisplayWidget.prototype = {
+ _active: false,
+ get active MessageDisplayWidget_get_active() {
+ return this._active;
+ },
+
+ /**
+ * Track whether the single message display pane is desired to be displayed
+ * (it is actually displayed when active, does't matter when not), or
+ * otherwise the multiple message display pane is desired to be displayed.
+ */
+ _singleMessageDisplay: null,
+ get singleMessageDisplay MessageDisplayWidget_get_singleMessageDisplay() {
+ // when null, assume single message display
+ return this._singleMessageDisplay != false;
+ },
+ set singleMessageDisplay MessageDisplayWidget_set_singleMessageDisplay(
+ aSingleDisplay) {
+ if (this._singleMessageDisplay != aSingleDisplay) {
+ this._singleMessageDisplay = aSingleDisplay;
+ if (this._active)
+ this._updateActiveMessagePane();
+ }
+ },
+
+ /**
+ * Set pane visibility based on this.singleMessageDisplay.
+ */
+ _updateActiveMessagePane: function MessageDisplayWidget_updateMessagePane() {
+ // _singleMessageDisplay can be null, so use the property (getter)
+ document.getElementById("singlemessage").hidden =
+ !this.singleMessageDisplay;
+ document.getElementById("multimessage").hidden =
+ this.singleMessageDisplay;
+ },
+
+ /**
+ * Cleanup the MessageDisplayWidget in preparation for going away. Called by
+ * FolderDisplayWidget's close method.
+ */
+ _close: function MessageDisplayWidget_close() {
+ // mark ourselves inactive without doing any work.
+ this._active = false;
+ },
+
+ /**
+ * The FolderDisplayWidget that owns us.
+ */
+ folderDisplay: null,
+ /**
+ * The currently displayed message's nsIMsgDBHdr. null if there's no message.
+ */
+ displayedMessage: null,
+
+ clearDisplay: function MessageDisplayWidget_clearDisplay() {
+ this.displayedMessage = null;
+ this.messageLoading = false;
+ this.messageLoaded = false;
+ ClearPendingReadTimer();
+ ClearMessagePane();
+ },
+
+ onCreatedView: function MessageDisplayWidget_onCreatedView() {
+ // we need to compel setting this because nsMsgSearchDBView defaults it on
+ this.folderDisplay.view.dbView.suppressMsgDisplay = !this.visible;
+ },
+
+ /**
+ * FolderDisplayWidget tells us when it is killing the view, which means our
+ * displayed message is no longer valid.
+ */
+ onDestroyingView: function MessageDisplayWidget_onDestroyingView(
+ aFolderIsComingBack) {
+ this.displayedMessage = null;
+ // The only time we want to do anything is when the folder is not coming
+ // back. If it is coming back, we can handle things when it shows up.
+ if (!aFolderIsComingBack && this._active) {
+ // and in this case, we just want to clear things.
+ this.clearDisplay();
+ this.singleMessageDisplay = true;
+ }
+ },
+
+ /**
+ * FolderDisplayWidget tells us when a message is being displayed.
+ */
+ onDisplayingMessage: function MessageDisplayWidget_onDisplayingMessage(
+ aMsgHdr) {
+ this.displayedMessage = aMsgHdr;
+ this.messageLoading = true;
+ this.messageLoaded = false;
+ },
+
+ /**
+ * The maximum number of messages to summarize at any given time. If there
+ * are more messages than this, we don't summarize, and instead give a blank
+ * window pane. Arguably something that says "there are two many messages"
+ * would be a better idea.
+ */
+ MAX_MESSAGES_TO_SUMMARIZE: 100,
+
+ /**
+ * FolderDisplayWidget tells us when the set of selected messages has changed.
+ * FDW is doing this because an nsMsgDBView/subclass called
+ * summarizeSelection. Although the call is purely an outgrowth of the
+ * introduction of folder summaries, it also provides a means for us to
+ * completely replace the nsMsgDBView logic. Namely, we get first bat at
+ * taking an action as a result of the selection change, and we can cause the
+ * nsMsgDBView to not do anything (by returning true).
+ * This notification will come prior to an onDisplayingMessage notification,
+ * and we will only get that notification if we return false and the
+ * nsMsgDBView logic wanted to display a message (read: there is exactly
+ * one message displayed and it wasn't already displayed.)
+ *
+ * The prime responsibilities of this function are:
+ * - To make sure the right message pane (single or multi) is displayed.
+ * - To kick off a multi-message or thread summarization if multiple messages
+ * are selected.
+ * - To throttle the rate at which we perform summarizations in case the
+ * selection is being updated frequently. This could be as a result of the
+ * user holding down shift and using their keyboard to expand the selection,
+ * use of the archive mechanism, or other.
+ * - To clear the message pane if no messages are selected. This used to be
+ * triggered by nsMsgDBView::SelectionChanged but is now our responsibility.
+ *
+ * In the event that the controlling preference for message summarization is
+ * not enabled (mail.operate_on_msgs_in_collapsed_threads), and there is a
+ * multi-selection, we just clear the display.
+ *
+ * @return true if we handled the selection notification and the nsMsgDBView
+ * should do nothing, false if we did not and the nsMsgDBView should use its
+ * logic to display a message.
+ */
+ onSelectedMessagesChanged:
+ function MessageDisplayWidget_onSelectedMessagesChanged() {
+ // If we are not active, we should not be touching things. pretend we are
+ // handling whatever is happening so the nsMsgDBView doesn't try and do
+ // anything. makeActive will trigger a fake SelectionChanged notification
+ // when we switch, which should put everything in its right place.
+ if (!this.active)
+ return true;
+
+ let selectedCount = this.folderDisplay.selectedCount;
+
+ if (selectedCount == 0) {
+ // davida, put your folder summary stuff here.
+ this.clearDisplay();
+ this.singleMessageDisplay = true;
+ }
+ else if (selectedCount == 1) {
+ // the display of the message is happening without us
+ this.singleMessageDisplay = true;
+
+ // This is the only case we don't handle and want the nsMsgDBView to
+ // take care of.
+ return false;
+ }
+ // we have a limit on the number of messages and if the pref is enabled
+ else if ((selectedCount < this.MAX_MESSAGES_TO_SUMMARIZE) &&
+ gPrefBranch.getBoolPref("mail.operate_on_msgs_in_collapsed_threads")) {
+ // _showSummary is responsible for handling the "don't resummarize too
+ // often" logic, as well as updating singleMessageDisplay.
+ this._showSummary();
+ }
+ // and so we clear things
+ else {
+ this.clearDisplay();
+ this.singleMessageDisplay = true;
+ }
+
+ return true;
+ },
+
+ /**
+ * If we are already summarized and we get a new request to summarize, require
+ * that the selection has been stable for at least this many milliseconds
+ * before resummarizing.
+ * Unit tests know about this variable and poke at it, so don't change the name
+ * without making sure you update the unit tests. (Not that you would commit
+ * code without first running all tests yourself...)
+ */
+ SUMMARIZATION_SELECTION_STABILITY_INTERVAL_MS: 100,
+ /**
+ * The timeout ID resulting from the call to window.setTimeout that we are
+ * using to require the selection be 'stabilized' before re-summarizing.
+ */
+ _summaryStabilityTimeout: null,
+
+ /**
+ * Updates message summaries with care to throttle the summarization when the
+ * selection is rapidly changing. We require that either we have not
+ * summarized anything 'recently', or that the selection has been stable for
+ * SUMMARIZATION_SELECTION_STABILITY_INTERVAL_MS ms before we update the
+ * summary. 'Recently' for our purposes means that it has been at least
+ * SUMMARIZATION_SELECTION_STABILITY_INTERVAL_MS ms since our last summary.
+ *
+ * Example event sequences (assuming a 100ms stability interval):
+ * - User selects a collapsed thread => we summarize the selection and set a
+ * 100ms timer set to call _clearSummaryTimer.
+ * - User extends the selection 50ms later => the timer has not elapsed so we
+ * reset it to 100ms to call _showSummary.
+ * - User extends the selection yet again 50ms later => timer has not elapsed
+ * so we reset it to 100ms to call _showSummary.
+ * - 100ms later, the timer elapses => we call _showSummary which updates the
+ * summary.
+ * - 2 seconds later, the user selects some other messages => we summarize the
+ * select and set a 100ms timer set to call _clearSummaryTimer.
+ * - 100ms later, _clearSummaryTimer clears _summaryStabilityTimeout so that
+ * the next selection change will immediately summarize.
+ *
+ * @param aIsCallback Is this a callback to ourselves? Callers should not set
+ * this, leaving it undefined.
+ */
+ _showSummary: function MessageDisplayWidget_showSummary(aIsCallback) {
+ // note: if we are in this function, we already know that summaries are
+ // enabled.
+
+ // If this is not a callback from the timeout and we currently have a
+ // timeout, that means that we need to wait for the selection to stabilize.
+ // The fact that we are getting called means the selection has just changed
+ // yet again and is not stable, so reset the timer for the full duration.
+ if (!aIsCallback && this._summaryStabilityTimeout != null) {
+ clearTimeout(this._summaryStabilityTimeout);
+ this._summaryStabilityTimeout =
+ setTimeout(this._wrapShowSummary,
+ this.SUMMARIZATION_SELECTION_STABILITY_INTERVAL_MS,
+ this);
+ return;
+ }
+
+ // Bail if our selection count has stabilized outside an acceptable range.
+ let selectedCount = this.folderDisplay.selectedCount;
+ if (selectedCount < 2 || selectedCount > this.MAX_MESSAGES_TO_SUMMARIZE)
+ return;
+
+ // Setup a timeout call to _clearSummaryTimer so that we don't try and
+ // summarize again within 100ms of now. Do this before calling
+ // the summarization logic in case it throws an exception.
+ this._summaryStabilityTimeout =
+ setTimeout(this._clearSummaryTimer,
+ this.SUMMARIZATION_SELECTION_STABILITY_INTERVAL_MS,
+ this);
+
+ // figure out if we're looking at one thread or more than one thread
+ let selectedMessages = this.folderDisplay.selectedMessages;
+ let firstThreadId = selectedMessages[0].threadId;
+ let oneThread = true;
+ for (let i = 0; i < selectedMessages.length; i++) {
+ if (selectedMessages[i].threadId != firstThreadId) {
+ oneThread = false;
+ break;
+ }
+ }
+ if (oneThread)
+ summarizeThread(selectedMessages);
+ else
+ summarizeMultipleSelection(selectedMessages);
+ this.singleMessageDisplay = false;
+ },
+ _wrapShowSummary: function MessageDisplayWidget__wrapShowSummary(aThis) {
+ aThis._showSummary(true);
+ },
+ /**
+ * Just clears the _summaryStabilityTimeout attribute so we can use it as a
+ * means of checking if we are allowed to display the summary immediately.
+ */
+ _clearSummaryTimer: function MessageDisplayWidget__clearSummaryTimer(aThis) {
+ aThis._summaryStabilityTimeout = null;
+ },
+
+ /**
+ * Called by the FolderDisplayWidget when it is being made active again and
+ * it's time for us to step up and re-display or clear the message as
+ * demanded by our multiplexed tab implementation.
+ */
+ makeActive: function MessageDisplayWidget_makeActive() {
+ let wasInactive = !this._active;
+ this._active = true;
+
+ if (wasInactive) {
+ // (see our usage below)
+ let preDisplayedMessage = this.displayedMessage;
+ // Force a synthetic selection changed event. This will propagate through
+ // to a call to onSelectedMessagesChanged who will handle making sure the
+ // right message pane is in use, etc.
+ this.folderDisplay.view.dbView.selectionChanged();
+ // The one potential problem is that the message view only triggers message
+ // streaming if it doesn't think the message is already displayed. In that
+ // case we need to force a re-display by calling reloadMessage. We can
+ // detect that case by seeing if our displayedMessage value changes its
+ // value during this call (because we will receive a onDisplayingMessage
+ // notification). If we should be displaying a single message but the
+ // value does not change, we need to force a re-display.
+ if (this.singleMessageDisplay && this.displayedMessage &&
+ (this.displayedMessage == preDisplayedMessage))
+ this.folderDisplay.view.dbView.reloadMessage();
+ }
+
+ this._updateActiveMessagePane();
+ },
+
+ /**
+ * Called by the FolderDisplayWidget when it is being made inactive or no
+ * longer requires messages to be displayed.
+ */
+ makeInactive: function MessageDisplayWidget_makeInactive() {
+ this._active = false;
+ }
+};
+
+/**
+ * Display widget abstraction for the 3-pane message view's preview pane/message
+ * pane. Like the DisplayWidget, it is multiplexed.
+ */
+function MessagePaneDisplayWidget() {
+ MessageDisplayWidget.call(this);
+ this._visible = true;
+}
+MessagePaneDisplayWidget.prototype = {
+ __proto__: MessageDisplayWidget.prototype,
+
+ get visible MessageDisplayWidget_get_visible() {
+ return this._visible;
+ },
+ /**
+ * Tell us whether the message pane is visible or not; this should reflect
+ * reality and does not define reality. (Setting this to false does not
+ * hide the message pane, it merely makes us think it is hidden.)
+ */
+ set visible MessageDisplayWidget_set_visible(aVisible) {
+ // Ignore this if we are inactive. We don't want to get faked out by things
+ // happening after our tab has closed up shop.
+ if (!this._active)
+ return;
+
+ // no-op if it's the same
+ if (aVisible == this._visible)
+ return;
+
+ this._visible = aVisible;
+ // Update suppression. If we were not visible and now are visible, the db
+ // view itself will handle triggering the message display for us if the
+ // message was not currently being displayed...
+ let dbView = this.folderDisplay.view.dbView;
+ if (dbView) {
+ let treeSelection = this.folderDisplay.treeSelection;
+ // flag if we need to force the redisplay manually...
+ let needToReloadMessage = treeSelection.count &&
+ dbView.currentlyDisplayedMessage == treeSelection.currentIndex;
+ dbView.suppressMsgDisplay = !this._visible;
+ if (needToReloadMessage)
+ dbView.reloadMessage();
+ }
+ // But if we are no longer visible, it's on us to clear the display.
+ if (!aVisible)
+ this.clearDisplay();
+ },
+};
+
+/**
+ * Message display widget for the "message" tab that is the tab-based equivalent
+ * of the standalone message window.
+ */
+function MessageTabDisplayWidget() {
+ MessageDisplayWidget.call(this);
+}
+MessageTabDisplayWidget.prototype = {
+ __proto__: MessageDisplayWidget.prototype,
+
+ /**
+ * The message tab always has a visible message pane.
+ */
+ get visible() {
+ return true;
+ },
+ set visible(aIgnored) {
+ },
+
+ onSelectedMessagesChanged:
+ function MessageTabDisplayWidget_onSelectedMessagesChanged() {
+ this.__proto__.__proto__.onSelectedMessagesChanged.apply(this, arguments);
+ if (!this.closing)
+ document.getElementById('tabmail').setTabTitle(this.folderDisplay._tabInfo);
+ },
+
+ /**
+ * A message tab should never ever be blank. Close the tab if we become
+ * blank.
+ */
+ clearDisplay: function MessageTabDisplayWidget_clearDisplay() {
+ if (!this.closing) {
+ this.closing = true;
+ document.getElementById('tabmail').closeTab(this.folderDisplay._tabInfo);
+ }
+ }
+};
+
+/**
+ * The search dialog has no message preview pane, and so wants a message
+ * display widget that is never visible. No one other than the search
+ * dialog should use this because the search dialog is bad UI.
+ */
+function NeverVisisbleMessageDisplayWidget() {
+ MessageDisplayWidget.call(this);
+}
+NeverVisisbleMessageDisplayWidget.prototype = {
+ __proto__: MessageDisplayWidget.prototype,
+ get visible() {
+ return false;
+ },
+ onSelectedMessagesChanged: function() {
+ return false;
+ },
+ _updateActiveMessagePane: function() {
+ },
+};
\ No newline at end of file
--- a/mail/base/content/messageWindow.js
+++ b/mail/base/content/messageWindow.js
@@ -1,129 +1,245 @@
-# -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is Mozilla Communicator client code, released
-# March 31, 1998.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 1998-1999
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
+/** ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
/* This is where functions related to the standalone message window are kept */
+Components.utils.import("resource://app/modules/jsTreeSelection.js");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
// from MailNewsTypes.h
const nsMsgKey_None = 0xFFFFFFFF;
const nsMsgViewIndex_None = 0xFFFFFFFF;
/* globals for a particular window */
-var gCurrentMessageUri;
-var gCurrentFolderUri;
-var gThreadPaneCommandUpdater = null;
-var gNextMessageViewIndexAfterDelete = -2;
-var gCurrentFolderToRerootForStandAlone;
-var gRerootOnFolderLoadForStandAlone = false;
-var gNextMessageAfterLoad = null;
-var gMessageToLoad = nsMsgKey_None;
+/// we have no tree view; let people know that.
+var gFolderTreeView = null;
+
+var gFolderDisplay;
+var gMessageDisplay;
+
+/**
+ * We subclass FolderDisplayWidget:
+ * - Because it assumes some thread-pane things that do not apply to us and we
+ * want to no-op those things out.
+ * - To intercept queries about the selected message so that we can do the
+ * .eml file thing. We were originally trying to avoid involving the
+ * nsMsgDBView, but that might not be important anymore. (future work)
+ */
+function StandaloneFolderDisplayWidget(aMessageDisplayWidget) {
+ FolderDisplayWidget.call(this, null, aMessageDisplayWidget);
+ // do not set the actual treeBox variable or our superclass might try and do
+ // weird rooting things we don't want to have to think about right now.
+ this._magicTreeSelection = new JSTreeSelection(this.treeBox);
+}
+StandaloneFolderDisplayWidget.prototype = {
+ __proto__: FolderDisplayWidget.prototype,
+
+ /**
+ * If we have a displayed message, then we've got 1 message, otherwise 0.
+ */
+ get selectedCount() {
+ return this.messageDisplay.displayedMessage ? 1 : 0;
+ },
+
+ /**
+ * If we have a selected message, it's the one displayed! This is more
+ * straight-forward than having you trace through the tree selection and
+ * db view logic.
+ */
+ get selectedMessage() {
+ return this.messageDisplay.displayedMessage;
+ },
-// the folderListener object
-var folderListener = {
- OnItemAdded: function(parentItem, item) {},
+ /**
+ * If we have a selected message, it's the one displayed! This is more
+ * straight-forward than having you trace through the tree selection and
+ * db view logic.
+ */
+ get selectedMessages() {
+ return this.messageDisplay.displayedMessage ?
+ [this.messageDisplay.displayedMessage] : [];
+ },
- OnItemRemoved: function(parentItem, item) {},
- OnItemPropertyChanged: function(item, property, oldValue, newValue) {},
- OnItemIntPropertyChanged: function(item, property, oldValue, newValue) {
- if (item.URI == gCurrentFolderUri) {
- if (property.toString() == "TotalMessages" || property.toString() == "TotalUnreadMessages") {
- UpdateStandAloneMessageCounts();
- }
+ /**
+ * We never have a real treeview, so we always want to tell the view about
+ * the fake tree box so it will actually do something in NoteChange.
+ */
+ onCreatedView:
+ function StandaloneMessageDisplayWidget_onCreatedView() {
+ this._fakeTreeBox.view = this.view.dbView;
+ this._magicTreeSelection.view = this.view.dbView;
+ // only if we're not dealing with a dummy message (from .eml file /
+ // attachment should we try and hook up the selection object.) Otherwise
+ // the view will not operate in stand alone message mode.
+ // XXX the sequencing here may break re-using a message window that is
+ // showing an .eml file to go to a real message, at least in terms of
+ // having the selection object properly associated with the tree.
+ if (!this.messageDisplay.isDummy) {
+ this.view.dbView.setTree(this._fakeTreeBox);
+ this.view.dbView.selection = this._magicTreeSelection;
}
+ this.__proto__.__proto__.onCreatedView.call(this);
+ },
+
+ _superSelectedMessageUrisGetter:
+ FolderDisplayWidget.prototype.__lookupGetter__('selectedMessageUris'),
+ /**
+ * Check with the message display widget to see if it has a dummy; if so, just
+ * return the dummy's URI, as the nsMsgDBView logic that our superclass uses
+ * falls down in that case.
+ */
+ get selectedMessageUris() {
+ if (this.messageDisplay.displayedUri)
+ return [this.messageDisplay.displayedUri];
+ return this._superSelectedMessageUrisGetter.call(this);
},
- OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) {},
- OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue){},
- OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) {},
+
+ /// folder display will want to show the thread pane; we need do nothing
+ _showThreadPane: function () {},
+ _showAccountCentral: function () {},
+
+ _updateThreadDisplay: function () {},
+
+ onMessageCountsChanged:
+ function StandaloneFolderDisplayWidget_onMessageCountsChaned() {
+ UpdateStatusMessageCounts();
+ },
+};
+
- OnItemEvent: function(folder, event) {
- var eventType = event.toString();
+/**
+ * Display widget abstraction for a standalone message display. Right now
+ * I think this means the standalone message window, and not the 'message in a
+ * tab' thing, which is really just a perverted configuration of the 3-pane
+ * format.
+ */
+function StandaloneMessageDisplayWidget() {
+ MessageDisplayWidget.call(this);
+ /**
+ * Indicate whether the message being displayed is a 'dummy' because it is
+ * backed not by an nsIMsgDBHdr but instead by a file on disk or an
+ * attachment on some mail message.
+ */
+ this.isDummy = false;
+ /**
+ * When displaying a dummy message, this is the URI of the message that we are
+ * displaying. If we are not displaying a dummy message, this is null.
+ */
+ this.displayedUri = null;
+}
+StandaloneMessageDisplayWidget.prototype = {
+ __proto__: MessageDisplayWidget.prototype,
+
+ /**
+ * The message pane is a standalone display widget is always visible.
+ */
+ get visible() {
+ return true;
+ },
+ set visible(aIgnored) {
+ },
- if (eventType == "DeleteOrMoveMsgCompleted")
- HandleDeleteOrMoveMsgCompleted(folder);
- else if (eventType == "DeleteOrMoveMsgFailed")
- HandleDeleteOrMoveMsgFailed(folder);
- else if (eventType == "FolderLoaded") {
- if (folder) {
- var uri = folder.URI;
- if (uri == gCurrentFolderToRerootForStandAlone) {
- gCurrentFolderToRerootForStandAlone = null;
- folder.endFolderLoading();
- if (gRerootOnFolderLoadForStandAlone) {
- RerootFolderForStandAlone(uri);
- }
- }
- }
+ /**
+ * Display the external message (from disk or attachment) named by the URI.
+ */
+ displayExternalMessage:
+ function StandaloneMessageDisplayWidget_displayExternalMessage(aUri) {
+ this.isDummy = true;
+ this.displayedUri = aUri;
+ this.onDisplayingMessage(messageHeaderSink.dummyMsgHeader);
+ UpdateMailToolbar("external message display");
+ // null out the selection on the view so it operates in stand alone mode
+ this.folderDisplay.view.dbView.selection = null;
+ this.folderDisplay.view.dbView.loadMessageByUrl(aUri);
+ },
+
+ clearDisplay: function () {
+ this.messageLoading = false;
+ this.messageLoaded = false;
+ window.close();
+ },
+ _updateActiveMessagePane: function() {
+ // no-op. the message pane is always visible.
+ },
+
+ onDisplayingMessage:
+ function StandaloneMessageDisplayWidget_onDisplayingMessage(aMsgHdr) {
+ this.__proto__.__proto__.onDisplayingMessage.call(this, aMsgHdr);
+
+ // - set the window title to the message subject (and maybe the app name)
+ let title = aMsgHdr.mime2DecodedSubject;
+ if (!gPlatformOSX)
+ title += " - " + gBrandBundle.getString("brandFullName");
+ document.title = title;
+
+ this.isDummy = aMsgHdr.folder == null;
+ if (!this.isDummy)
+ this.displayedUri = null;
+ },
+
+ onSelectedMessagesChanged: function () {
+ if (this.folderDisplay.treeSelection.count == 0) {
+ window.close();
+ return true;
}
- else if (eventType == "JunkStatusChanged") {
- HandleJunkStatusChanged(folder);
- }
- }
-}
+ return false;
+ },
+};
var messagepaneObserver = {
canHandleMultipleItems: false,
onDrop: function (aEvent, aData, aDragSession)
{
var sourceUri = aData.data;
- if (sourceUri != gCurrentMessageUri)
+ if (!gFolderDisplay.selectedMessage ||
+ sourceUri != gFolderDisplay.selectedMessageUris[0])
{
- var msgHdr = GetMsgHdrFromUri(sourceUri);
-
- // Reset the window's message uri and folder uri vars, and
- // update the command handlers to what's going to be used.
- // This has to be done before the call to CreateView().
- gCurrentMessageUri = sourceUri;
- gCurrentFolderUri = msgHdr.folder.URI;
- UpdateMailToolbar('onDrop');
-
- // even if the folder uri's match, we can't use the existing view
- // (msgHdr.folder.URI == windowID.gCurrentFolderUri)
- // the reason is quick search and mail views.
- // see bug #187673
- CreateView(aDragSession.sourceNode.ownerDocument.defaultView.gDBView);
- LoadMessageByMsgKey(msgHdr.messageKey);
+ var msgHdr = messenger.msgHdrFromURI(sourceUri);
+ let originGlobal = aDragSession.sourceNode.ownerDocument.defaultValue;
+ gFolderDisplay.cloneView(originGlobal.gFolderDisplay.view);
+ gFolderDisplay.selectMessage(msgHdr);
}
},
onDragOver: function (aEvent, aFlavour, aDragSession)
{
var messagepanebox = document.getElementById("messagepanebox");
messagepanebox.setAttribute("dragover", "true");
},
@@ -144,295 +260,144 @@ var messagepaneObserver = {
getSupportedFlavours: function ()
{
var flavourSet = new FlavourSet();
flavourSet.appendFlavour("text/x-moz-message");
return flavourSet;
}
};
-function nsMsgDBViewCommandUpdater()
-{}
-
-function UpdateStandAloneMessageCounts()
+function UpdateStatusMessageCounts()
{
// hook for extra toolbar items
var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
observerService.notifyObservers(window, "mail:updateStandAloneMessageCounts", "");
}
-nsMsgDBViewCommandUpdater.prototype =
-{
- updateCommandStatus : function()
- {
- // the back end is smart and is only telling us to update command status
- // when the # of items in the selection has actually changed.
- UpdateMailToolbar("dbview, std alone window");
- },
-
- displayMessageChanged : function(aFolder, aSubject, aKeywords)
- {
- setTitleFromFolder(aFolder, aSubject);
- ClearPendingReadTimer(); // we are loading / selecting a new message so kill the mark as read timer for the currently viewed message
- gCurrentMessageUri = gDBView.URIForFirstSelectedMessage;
- UpdateStandAloneMessageCounts();
- goUpdateCommand("button_delete");
- goUpdateCommand("button_junk");
- goUpdateCommand("button_goBack");
- goUpdateCommand("button_goForward");
- goUpdateCommand("button_reply");
- goUpdateCommand("button_replyall");
- goUpdateCommand("button_replylist");
- },
-
- updateNextMessageAfterDelete : function()
- {
- SetNextMessageAfterDelete();
- },
-
- summarizeSelection : function() {return false},
-
- QueryInterface : function(iid)
- {
- if (iid.equals(Components.interfaces.nsIMsgDBViewCommandUpdater) ||
- iid.equals(Components.interfaces.nsISupports))
- return this;
-
- throw Components.results.NS_NOINTERFACE;
- }
-}
-
-function HandleDeleteOrMoveMsgCompleted(folder)
-{
- if ((folder.URI == gCurrentFolderUri))
- {
- gDBView.onDeleteCompleted(true);
- if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None)
- {
- var nextMstKey = gDBView.getKeyAt(gNextMessageViewIndexAfterDelete);
-
- if (pref.getBoolPref("mail.close_message_window.on_delete")) {
- // Tell the main window to select the next message since we
- // won't be viewing it automatically in the standalone window.
- var treeView = window.opener.document.getElementById("threadTree").view;
- if (gDBView.removeRowOnMoveOrDelete && gNextMessageViewIndexAfterDelete >= 0) {
- gDBView.suppressCommandUpdating = true;
- treeView.selection.select(gNextMessageViewIndexAfterDelete);
- treeView.selectionChanged();
-
- window.opener.EnsureRowInThreadTreeIsVisible(gNextMessageViewIndexAfterDelete);
- gDBView.suppressCommandUpdating = false;
- }
-
- window.close();
- }
- else if (nextMstKey != nsMsgKey_None) {
- LoadMessageByViewIndex(gNextMessageViewIndexAfterDelete);
- }
- else {
- window.close();
- }
- }
- else
- {
- // close the stand alone window because there are no more messages in the folder
- window.close();
- }
- }
-}
-
-function HandleDeleteOrMoveMsgFailed(folder)
-{
- gDBView.onDeleteCompleted(false);
-}
-
-function IsCurrentLoadedFolder(folder)
-{
- return (folder.URI == gCurrentFolderUri);
-}
-
-
// we won't show the window until the onload() handler is finished
// so we do this trick (suggested by hyatt / blaker)
function OnLoadMessageWindow()
{
setTimeout(delayedOnLoadMessageWindow, 0); // when debugging, set this to 5000, so you can see what happens after the window comes up.
}
function delayedOnLoadMessageWindow()
{
HideMenus();
ShowMenus();
MailOfflineMgr.init();
CreateMailWindowGlobals();
verifyAccounts(null);
+ /**
+ * Create a message listener so that we can update the title once the message
+ * finishes streaming when it's a dummy.
+ */
+ gMessageListeners.push({
+ onStartHeaders: function () {},
+ onEndHeaders: function() {
+ if (gMessageDisplay.isDummy)
+ gMessageDisplay.onDisplayingMessage(messageHeaderSink.dummyMsgHeader);
+ UpdateMailToolbar(".eml/message from attachment finished loading");
+ },
+ onEndAttachments: function () {},
+ });
+
InitMsgWindow();
messenger.setWindow(window, msgWindow);
// FIX ME - later we will be able to use onload from the overlay
OnLoadMsgHeaderPane();
- var nsIFolderListener = Components.interfaces.nsIFolderListener;
- var notifyFlags = nsIFolderListener.removed | nsIFolderListener.event | nsIFolderListener.intPropertyChanged;
- Components.classes["@mozilla.org/messenger/services/session;1"]
- .getService(Components.interfaces.nsIMsgMailSession)
- .AddFolderListener(folderListener, notifyFlags);
-
- var originalView = null;
- var folder = null;
- var messageUri;
- var loadCustomMessage = false; //set to true when either loading a message/rfc822 attachment or a .eml file
- if (window.arguments)
- {
- if (window.arguments[0])
- {
- try
- {
- messageUri = window.arguments[0];
- if (messageUri instanceof Components.interfaces.nsIURI)
- {
- loadCustomMessage = /type=application\/x-message-display/.test(messageUri.spec);
- gCurrentMessageUri = messageUri.spec;
- if (messageUri.folder && messageUri instanceof Components.interfaces.nsIMsgMailNewsUrl)
- folder = messageUri.folder;
- }
- }
- catch(ex)
- {
- folder = null;
- dump("## ex=" + ex + "\n");
- }
-
- if (!gCurrentMessageUri)
- gCurrentMessageUri = window.arguments[0];
- }
- else
- gCurrentMessageUri = null;
-
- if (window.arguments[1])
- gCurrentFolderUri = window.arguments[1];
- else
- gCurrentFolderUri = folder ? folder.URI : null;
-
- if (window.arguments[2])
- originalView = window.arguments[2];
-
- }
-
- CreateView(originalView);
-
gPhishingDetector.init();
// initialize the customizeDone method on the customizeable toolbar
var toolbox = document.getElementById("mail-toolbox");
toolbox.customizeDone = function(aEvent) { MailToolboxCustomizeDone(aEvent, "CustomizeMailToolbar"); };
var toolbarset = document.getElementById('customToolbars');
toolbox.toolbarset = toolbarset;
- setTimeout(OnLoadMessageWindowDelayed, 0, loadCustomMessage);
+ SetupCommandUpdateHandlers();
- SetupCommandUpdateHandlers();
+ gMessageDisplay = new StandaloneMessageDisplayWidget();
+ gFolderDisplay = new StandaloneFolderDisplayWidget(gMessageDisplay);
+ gFolderDisplay.msgWindow = msgWindow;
+ gFolderDisplay.messenger = messenger;
+
+ setTimeout(actuallyLoadMessage, 0);
}
-function OnLoadMessageWindowDelayed(loadCustomMessage)
-{
- if (loadCustomMessage)
- {
- gDBView.suppressMsgDisplay = false;
- gDBView.loadMessageByUrl(gCurrentMessageUri);
- }
- else
+function actuallyLoadMessage() {
+ /*
+ * Our actual use cases that drive the arguments we take are:
+ * 1) Displaying a message from disk or that was an attachment on a message.
+ * Such messages have no (real) message header and must come in the form of
+ * a URI. (The message display code creates a 'dummy' header.)
+ * 2) Displaying a message that has a header available, either as a result of
+ * the user selecting a message in another window to spawn us or through
+ * some indirection like displaying a message by message-id. (The
+ * newsgroup UI exposes this, as well as the spotlight/vista indexers.)
+ *
+ * We clone views when possible for:
+ * - Consistency of navigation within the message display. Users would find
+ * it odd if they showed a message from a cross-folder view but ended up
+ * navigating around the message's actual folder.
+ * - Efficiency. It's faster to clone a view than open a new one.
+ *
+ * Our argument idioms for the use cases are thus:
+ * 1) [A Message URI] where the URI is an nsIURL corresponding to a message
+ * on disk or that is an attachment part on another message.
+ * 2) [A Message header, (optional) the origin DBViewWraper]
+ *
+ * Our original set of arguments, in case these get passed in and you're
+ * wondering why we explode, was:
+ * 0: A message URI, string or nsIURI.
+ * 1: A folder URI. If arg 0 was an nsIURI, it may have had a folder attribute.
+ * 2: The nsIMsgDBView used to open us.
+ */
+ if (window.arguments && window.arguments.length)
{
- var msgKey = extractMsgKeyFromURI(gCurrentMessageUri);
- var viewIndex = gDBView.findIndexFromKey(msgKey, true);
- // the message may not appear in the view if loaded from a search dialog
- if (viewIndex != nsMsgViewIndex_None)
- LoadMessageByViewIndex(viewIndex);
- else
- messenger.openURL(gCurrentMessageUri);
+ // message header?
+ if (window.arguments[0] instanceof Components.interfaces.nsIMsgDBHdr) {
+ let msgHdr = window.arguments[0];
+ let originViewWrapper = window.arguments.length > 1 ?
+ window.arguments[1] : null;
+ if (originViewWrapper)
+ gFolderDisplay.cloneView(originViewWrapper);
+ else
+ gFolderDisplay.show(msgHdr.folder);
+ gFolderDisplay.selectMessage(msgHdr);
+ }
+ // it must be a URI for a message lacking a backing header
+ else {
+ // Here's how this goes. nsMessenger::LoadURL checks out the URL we
+ // pass it, and if it sees that the URI starts with "file:" or contains
+ // "type=application/x-message-display" then it knows it needs to
+ // create a dummy header. It gets the 'dummyMsgHeader' property from
+ // the js message header sink.
+ // Additionally, nsMessenger::MsgHdrFromURI checks the URI we pass it
+ // and if it meets either of those same constraints (assuming it has a
+ // msgWindow), it will retrieve the header sink off the msgWindow, get
+ // the dummy header, and return that.
+ // so...
+ // - create a search view for the standalone dude
+ gFolderDisplay.view.openSearchView();
+ // - load the message
+ let messageURI = window.arguments[0];
+ if (messageURI instanceof Components.interfaces.nsIURI)
+ messageURI = messageURI.spec;
+ gMessageDisplay.displayExternalMessage(messageURI);
+ }
}
- gNextMessageViewIndexAfterDelete = gDBView.msgToSelectAfterDelete;
- UpdateStandAloneMessageCounts();
+
+ gFolderDisplay.makeActive();
// set focus to the message pane
window.content.focus();
-
- // since we just changed the pane with focus we need to update the toolbar to reflect this
- // XXX TODO
- // can we optimize
- // and just update cmd_delete and button_delete?
- UpdateMailToolbar("focus");
-}
-
-function CreateView(originalView)
-{
- var msgFolder = GetLoadedMsgFolder();
-
- // extract the sort type, the sort order,
- var sortType;
- var sortOrder;
- var viewFlags;
- var viewType;
-
- if (originalView)
- {
- viewType = originalView.viewType;
- viewFlags = originalView.viewFlags;
- sortType = originalView.sortType;
- sortOrder = originalView.sortOrder;
- }
- else if (msgFolder)
- {
- var msgDatabase = msgFolder.msgDatabase;
- if (msgDatabase)
- {
- var dbFolderInfo = msgDatabase.dBFolderInfo;
- sortType = dbFolderInfo.sortType;
- sortOrder = dbFolderInfo.sortOrder;
- viewFlags = dbFolderInfo.viewFlags;
- viewType = dbFolderInfo.viewType;
- msgDatabase = null;
- dbFolderInfo = null;
- }
- }
- else
- {
- // this is a hack to make opening a stand-alone msg window on a
- // .eml file work. We use a search view since its much more tolerant
- // of not having a folder.
- viewType = nsMsgViewType.eShowSearch;
- }
-
- // create a db view
- CreateBareDBView(originalView, msgFolder, viewType, viewFlags, sortType, sortOrder);
-
- var uri;
- if (gCurrentMessageUri)
- uri = gCurrentMessageUri;
- else if (gCurrentFolderUri)
- uri = gCurrentFolderUri;
- else
- uri = null;
-
- SetUpToolbarButtons(uri);
-
- // hook for extra toolbar items
- var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
- observerService.notifyObservers(window, "mail:setupToolbarItems", uri);
-}
-
-function extractMsgKeyFromURI()
-{
- var msgKey = -1;
- var msgHdr = messenger.msgHdrFromURI(gCurrentMessageUri);
- if (msgHdr)
- msgKey = msgHdr.messageKey;
- return msgKey;
}
function ShowMenus()
{
var openMail3Pane_menuitem = document.getElementById('tasksMenuMail');
if (openMail3Pane_menuitem)
openMail3Pane_menuitem.removeAttribute("hidden");
}
@@ -529,256 +494,108 @@ function HideMenus()
var menuFileClose = document.getElementById('menu_close');
var menuFileQuit = document.getElementById('menu_FileQuitItem');
if (menuFileClose && menuFileQuit)
menuFileQuit.parentNode.replaceChild(menuFileClose, menuFileQuit);
}
function OnUnloadMessageWindow()
{
+ gFolderDisplay.close();
UnloadCommandUpdateHandlers();
// FIX ME - later we will be able to use onunload from the overlay
OnUnloadMsgHeaderPane();
gPhishingDetector.shutdown();
OnMailWindowUnload();
}
function GetSelectedMsgFolders()
{
- var folderArray = [];
- var msgFolder = GetLoadedMsgFolder();
- if (msgFolder)
- folderArray[0] = msgFolder;
-
- return folderArray;
-}
-
-function GetFirstSelectedMessage()
-{
- return GetLoadedMessage();
+ if (gFolderDisplay.displayedFolder)
+ return [gFolderDisplay.displayedFolder];
+ return [];
}
function GetNumSelectedMessages()
{
- if (gCurrentMessageUri)
- return 1;
- else
- return 0;
-}
-
-function GetSelectedMessages()
-{
- var messageArray = new Array(1);
- var message = GetLoadedMessage();
- if (message)
- messageArray[0] = message;
-
- return messageArray;
-}
-
-function GetSelectedIndices(dbView)
-{
- try {
- return dbView.getIndicesForSelection({});
- }
- catch (ex) {
- dump("ex = " + ex + "\n");
- return null;
- }
-}
-
-function GetLoadedMsgFolder()
-{
- return (gCurrentFolderUri) ? GetMsgFolderFromUri(gCurrentFolderUri) : null;
-}
-
-function GetSelectedFolderURI()
-{
- return gCurrentFolderUri;
-}
-
-function GetLoadedMessage()
-{
- return gCurrentMessageUri;
-}
-
-//Clear everything related to the current message. called after load start page.
-function ClearMessageSelection()
-{
- gCurrentMessageUri = null;
- gCurrentFolderUri = null;
- UpdateMailToolbar("clear msg, std alone window");
-}
-
-function SetNextMessageAfterDelete()
-{
- gNextMessageViewIndexAfterDelete = gDBView.msgToSelectAfterDelete;
-}
-
-function SelectFolder(folderUri)
-{
- if (folderUri == gCurrentFolderUri)
- return;
-
- var msgfolder = GetMsgFolderFromUri(folderUri)
- if (!msgfolder || msgfolder.isServer)
- return;
-
- // close old folder view
- var dbview = GetDBView();
- if (dbview)
- dbview.close();
-
- gCurrentFolderToRerootForStandAlone = folderUri;
- msgWindow.openFolder = msgfolder;
-
- if (msgfolder.manyHeadersToDownload)
- {
- gRerootOnFolderLoadForStandAlone = true;
- try
- {
- // accessing the db causes the folder loaded notification to get sent
- // for local folders.
- var db = msgfolder.msgDatabase;
- msgfolder.startFolderLoading();
- msgfolder.updateFolder(msgWindow);
- }
- catch(ex)
- {
- dump("Error loading with many headers to download: " + ex + "\n");
- }
- }
- else
- {
- RerootFolderForStandAlone(folderUri);
- gRerootOnFolderLoadForStandAlone = false;
- msgfolder.startFolderLoading();
-
- //Need to do this after rerooting folder. Otherwise possibility of receiving folder loaded
- //notification before folder has actually changed.
- msgfolder.updateFolder(msgWindow);
- }
-}
-
-function RerootFolderForStandAlone(uri)
-{
- gCurrentFolderUri = uri;
-
- // create new folder view
- CreateView(null);
-
- if (gMessageToLoad != nsMsgKey_None)
- {
- LoadMessageByMsgKey(gMessageToLoad);
- gMessageToLoad = nsMsgKey_None;
- }
- // now do the work to load the appropriate message
- else if (gNextMessageAfterLoad) {
- var type = gNextMessageAfterLoad;
- gNextMessageAfterLoad = null;
- LoadMessageByNavigationType(type);
- }
-
- SetUpToolbarButtons(gCurrentFolderUri);
-
- UpdateMailToolbar("reroot folder in stand alone window");
-
- // hook for extra toolbar items
- var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
- observerService.notifyObservers(window, "mail:setupToolbarItems", uri);
-}
-
-function GetMsgHdrFromUri(messageUri)
-{
- return messenger.msgHdrFromURI(messageUri);
-}
-
-function SelectMessage(messageUri)
-{
- var msgHdr = GetMsgHdrFromUri(messageUri);
- LoadMessageByMsgKey(msgHdr.messageKey);
+ return gFolderDisplay.treeSelection.count;
}
function ReloadMessage()
{
- gDBView.reloadMessage();
+ gFolderDisplay.view.dbView.reloadMessage();
}
function MsgDeleteMessageFromMessageWindow(reallyDelete, fromToolbar)
{
// if from the toolbar, return right away if this is a news message
// only allow cancel from the menu: "Edit | Cancel / Delete Message"
- if (fromToolbar)
- {
- if (isNewsURI(gCurrentFolderUri))
- {
- // if news, don't delete
- return;
- }
- }
+ if (fromToolbar && gDisplayFolder.view.isNewsFolder)
+ return;
- // before we delete
- SetNextMessageAfterDelete();
+ gFolderDisplay.hintAboutToDeleteMessages();
if (reallyDelete)
- gDBView.doCommand(nsMsgViewCommandType.deleteNoTrash);
+ gFolderDisplay.doCommand(nsMsgViewCommandType.deleteNoTrash);
else
- gDBView.doCommand(nsMsgViewCommandType.deleteMsg);
+ gFolderDisplay.doCommand(nsMsgViewCommandType.deleteMsg);
}
// MessageWindowController object (handles commands when one of the trees does not have focus)
var MessageWindowController =
{
supportsCommand: function(command)
{
switch ( command )
{
+ // external messages cannot be deleted, mutated, or subjected to filtering
case "cmd_delete":
- case "cmd_undo":
- case "cmd_redo":
case "cmd_killThread":
case "cmd_killSubthread":
case "cmd_watchThread":
case "button_delete":
case "button_junk":
case "cmd_shiftDelete":
- case "cmd_saveAsFile":
- case "cmd_saveAsTemplate":
- case "cmd_viewPageSource":
- case "cmd_getMsgsForAuthAccounts":
case "cmd_tag":
case "button_mark":
case "cmd_markAsRead":
case "cmd_markAllRead":
case "cmd_markThreadAsRead":
case "cmd_markReadByDate":
case "cmd_markAsFlagged":
- case "button_file":
- case "cmd_file":
case "cmd_markAsJunk":
case "cmd_markAsNotJunk":
case "cmd_recalculateJunkScore":
case "cmd_applyFiltersToSelection":
case "cmd_applyFilters":
case "cmd_runJunkControls":
case "cmd_deleteJunk":
+ return !gMessageDisplay.isDummy;
+ case "cmd_undo":
+ case "cmd_redo":
+ case "cmd_saveAsFile":
+ case "cmd_saveAsTemplate":
+ case "cmd_viewPageSource":
+ case "cmd_getMsgsForAuthAccounts":
+ case "button_file":
+ case "cmd_file":
case "cmd_nextMsg":
case "button_next":
case "button_previous":
case "cmd_nextUnreadMsg":
case "cmd_nextFlaggedMsg":
case "cmd_nextUnreadThread":
case "cmd_previousMsg":
case "cmd_previousUnreadMsg":
case "cmd_previousFlaggedMsg":
case "cmd_goForward":
case "cmd_goBack":
case "button_goForward":
case "button_goBack":
- return !(gDBView.keyForFirstSelectedMessage == nsMsgKey_None);
+ return gFolderDisplay.selectedMessage != null;
case "cmd_reply":
case "button_reply":
case "cmd_replySender":
case "cmd_replyGroup":
case "cmd_replyall":
case "button_replyall":
case "cmd_replylist":
@@ -817,54 +634,60 @@ var MessageWindowController =
return MailOfflineMgr.isOnline();
default:
return false;
}
},
isCommandEnabled: function(command)
{
+ let loadedFolder;
switch ( command )
{
case "cmd_createFilterFromPopup":
case "cmd_createFilterFromMenu":
- var loadedFolder = GetLoadedMsgFolder();
+ loadedFolder = gFolderDisplay.displayedFolder;
if (!(loadedFolder && loadedFolder.server.canHaveFilters))
return false;
case "cmd_delete":
UpdateDeleteCommand();
// fall through
case "button_delete":
UpdateDeleteToolbarButton();
// fall through
case "cmd_shiftDelete":
- var loadedFolder = GetLoadedMsgFolder();
- return gCurrentMessageUri && loadedFolder && (loadedFolder.canDeleteMessages || isNewsURI(gCurrentFolderUri));
+ return gFolderDisplay.selectedMessage &&
+ gFolderDisplay.displayedFolder &&
+ (gFolderDisplay.displayedFolder.canDeleteMessages ||
+ gFolderDisplay.view.isNewsFolder);
case "button_junk":
UpdateJunkToolbarButton();
// fall through
case "cmd_markAsJunk":
case "cmd_markAsNotJunk":
case "cmd_recalculateJunkScore":
// can't do junk on news yet
- return (!isNewsURI(gCurrentFolderUri));
+ return (!gFolderDisplay.view.isNewsFolder);
case "button_archive":
- var folder = GetLoadedMsgFolder();
+ var folder = gFolderDisplay.displayedFolder;
return folder &&
!(IsSpecialFolder(folder, Components.interfaces.nsMsgFolderFlags.Archive,
true));
- case "cmd_archive":
case "cmd_reply":
case "button_reply":
+ return gFolderDisplay.selectedMessage && IsReplyEnabled();
+ case "cmd_replyall":
+ case "button_replyall":
+ return gFolderDisplay.selectedMessage && IsReplyAllEnabled();
+ case "cmd_replylist":
+ case "button_replylist":
+ return gFolderDisplay.selectedMessage && IsReplyListEnabled();
+ case "cmd_archive":
case "cmd_replySender":
case "cmd_replyGroup":
- case "cmd_replyall":
- case "button_replyall":
- case "cmd_replylist":
- case "button_replylist":
case "cmd_forward":
case "button_forward":
case "cmd_forwardInline":
case "cmd_forwardAttachment":
case "cmd_editAsNew":
case "cmd_print":
case "cmd_printpreview":
case "button_print":
@@ -878,17 +701,17 @@ var MessageWindowController =
case "cmd_markAsRead":
case "cmd_markAllRead":
case "cmd_markThreadAsRead":
case "cmd_markReadByDate":
return(true);
case "cmd_markAsFlagged":
case "button_file":
case "cmd_file":
- return ( gCurrentMessageUri != null);
+ return ( gFolderDisplay.selectedMessage != null);
case "cmd_printSetup":
return true;
case "cmd_getNewMessages":
case "button_getNewMessages":
case "cmd_getMsgsForAuthAccounts":
// GetMsgs should always be enabled, see bugs 89404 and 111102.
return true;
case "cmd_getNextNMessages":
@@ -917,30 +740,29 @@ var MessageWindowController =
case "cmd_fullZoomEnlarge":
case "cmd_fullZoomReset":
case "cmd_fullZoomToggle":
return true;
case "button_goForward":
case "button_goBack":
case "cmd_goForward":
case "cmd_goBack":
- return gDBView &&
- gDBView.navigateStatus((command == "cmd_goBack" ||
- command == "button_goBack")
- ? nsMsgNavigationType.back : nsMsgNavigationType.forward);
+ return gFolderDisplay.navigateStatus(
+ (command == "cmd_goBack" || command == "button_goBack") ?
+ nsMsgNavigationType.back : nsMsgNavigationType.forward);
case "cmd_search":
- var loadedFolder = GetLoadedMsgFolder();
+ loadedFolder = gFolderDisplay.displayedFolder;
if (!loadedFolder)
return false;
return loadedFolder.server.canSearchMessages;
case "cmd_undo":
case "cmd_redo":
return SetupUndoRedoCommand(command);
case "cmd_moveToFolderAgain":
- var loadedFolder = GetLoadedMsgFolder();
+ loadedFolder = gFolderDisplay.displayedFolder;
if (!loadedFolder || (pref.getBoolPref("mail.last_msg_movecopy_was_move") &&
!loadedFolder.canDeleteMessages))
return false;
return pref.getCharPref("mail.last_msg_movecopy_target_uri");
case "cmd_applyFilters":
case "cmd_runJunkControls":
case "cmd_deleteJunk":
return false;
@@ -1039,17 +861,17 @@ var MessageWindowController =
break;
case "cmd_saveAsFile":
MsgSaveAsFile();
break;
case "cmd_saveAsTemplate":
MsgSaveAsTemplate();
break;
case "cmd_viewPageSource":
- ViewPageSource(GetSelectedMessages());
+ ViewPageSource(gFolderDisplay.selectedMessageUris);
break;
case "cmd_reload":
ReloadMessage();
break;
case "cmd_find":
document.getElementById("FindToolbar").onFindCommand();
break;
case "cmd_findAgain":
@@ -1062,17 +884,17 @@ var MessageWindowController =
MsgSearchMessages();
break;
case "button_mark":
case "cmd_markAsRead":
MsgMarkMsgAsRead();
return;
case "cmd_markThreadAsRead":
ClearPendingReadTimer();
- gDBView.doCommand(nsMsgViewCommandType.markThreadRead);
+ gFolderDisplay.doCommand(nsMsgViewCommandType.markThreadRead);
return;
case "cmd_markAllRead":
MsgMarkAllRead();
return;
case "cmd_markReadByDate":
MsgMarkReadByDate();
return;
case "cmd_markAsFlagged":
@@ -1083,20 +905,22 @@ var MessageWindowController =
return;
case "cmd_markAsNotJunk":
JunkSelectedMessages(false);
return;
case "cmd_recalculateJunkScore":
analyzeMessagesForJunk();
return;
case "cmd_downloadFlagged":
- gDBView.doCommand(nsMsgViewCommandType.downloadFlaggedForOffline);
+ gFolderDisplay.doCommand(
+ nsMsgViewCommandType.downloadFlaggedForOffline);
return;
case "cmd_downloadSelected":
- gDBView.doCommand(nsMsgViewCommandType.downloadSelectedForOffline);
+ gFolderDisplay.doCommand(
+ nsMsgViewCommandType.downloadSelectedForOffline);
return;
case "cmd_synchronizeOffline":
MsgSynchronizeOffline();
return;
case "cmd_settingsOffline":
MailOfflineMgr.openOfflineAccountSettings();
return;
case "cmd_nextUnreadMsg":
@@ -1146,91 +970,36 @@ var MessageWindowController =
}
},
onEvent: function(event)
{
}
};
-function LoadMessageByNavigationType(type)
-{
- var resultId = new Object;
- var resultIndex = new Object;
- var threadIndex = new Object;
-
- gDBView.viewNavigate(type, resultId, resultIndex, threadIndex, true /* wrap */);
-
- // if we found something....display it.
- if ((resultId.value != nsMsgKey_None) && (resultIndex.value != nsMsgKey_None))
- {
- // load the message key
- LoadMessageByMsgKey(resultId.value);
- // if we changed folders, the message counts changed.
- UpdateStandAloneMessageCounts();
-
- // new message has been loaded
- return true;
- }
-
- // no message found to load
- return false;
-}
-
function performNavigation(type)
{
// Try to load a message by navigation type if we can find
// the message in the same folder.
- if (LoadMessageByNavigationType(type))
+ if (gFolderDisplay.navigate(type))
return;
CrossFolderNavigation(type);
}
function SetupCommandUpdateHandlers()
{
top.controllers.insertControllerAt(0, MessageWindowController);
}
function UnloadCommandUpdateHandlers()
{
top.controllers.removeController(MessageWindowController);
}
-function GetDBView()
-{
- return gDBView;
-}
-
-function LoadMessageByMsgKey(messageKey)
-{
- LoadMessageByViewIndex(gDBView.findIndexFromKey(messageKey, true));
-}
-
-function LoadMessageByViewIndex(viewIndex)
-{
- gDBView.loadMessageByViewIndex(viewIndex);
- // we only want to update the toolbar if there was no previous selected message.
- if (nsMsgKey_None == gDBView.keyForFirstSelectedMessage)
- UpdateMailToolbar("update toolbar for message Window");
-}
-
-function LoadNavigatedToMessage(msgHdr, folder, folderUri)
-{
- if (IsCurrentLoadedFolder(folder))
- {
- LoadMessageByMsgKey(msgHdr.messageKey);
- }
- else
- {
- gMessageToLoad = msgHdr.messageKey;
- SelectFolder(folderUri);
- }
-}
-
function getMailToolbox ()
{
return document.getElementById("mail-toolbox");
}
function RestoreFocusAfterHdrButton()
{
// set focus to the message pane
--- a/mail/base/content/messageWindow.xul
+++ b/mail/base/content/messageWindow.xul
@@ -53,16 +53,19 @@
<!ENTITY % customizeToolbarDTD SYSTEM "chrome://global/locale/customizeToolbar.dtd">
%customizeToolbarDTD;
#ifdef MOZILLA_1_9_1_BRANCH
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
#endif
]>
+<!--
+ - This window displays a single message.
+ -->
<window id="messengerWindow"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&titledefault.label;"
titlemodifier="&titledefault.label;"
titlemenuseparator="&titleSeparator.label;"
onload="OnLoadMessageWindow()"
onunload="OnUnloadMessageWindow()"
width="750"
@@ -73,16 +76,18 @@
<stringbundleset id="stringbundleset">
<stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
<stringbundle id="bundle_offlinePrompts" src="chrome://messenger/locale/offline.properties"/>
</stringbundleset>
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/shareglue.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/commandglue.js"/>
+ <script type="application/x-javascript" src="chrome://messenger/content/folderDisplay.js"/>
+ <script type="application/x-javascript" src="chrome://messenger/content/messageDisplay.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/mailWindow.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/messageWindow.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/accountUtils.js"/>
<script type="application/x-javascript" src="chrome://global/content/contentAreaUtils.js"/>
<script type="application/x-javascript" src="chrome://communicator/content/nsContextMenu.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/mailContextMenus.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/phishingDetector.js"/>
<script type="application/x-javascript" src="chrome://communicator/content/contentAreaClick.js"/>
@@ -172,22 +177,16 @@
<deck id="msgNotificationBar"/>
<!-- message view -->
<browser id="messagepane" context="mailContext" tooltip="aHTMLTooltip"
style="height: 0px; min-height: 1px" flex="1" name="messagepane"
disablesecurity="true" disablehistory="true" type="content-primary"
onresize="return messagePaneOnResize(event);" autofind="false"
src="about:blank" onclick="return contentAreaClick(event);" />
- <iframe id="htmlpane"
- style="height: 0px; min-height: 1px" flex="1" name="htmlpane"
- hidden="true"
- disablesecurity="true" disablehistory="true"
- onresize="return htmlPaneOnResize(event);" autofind="false"
- src="about:blank"/>
<splitter id="attachment-splitter" collapse="after" resizebefore="closest" resizeafter="closest" collapsed="true"/>
<hbox id="attachmentView"/>
<findbar id="FindToolbar" browserid="messagepane"/>
</vbox>
<panel id="customizeToolbarSheetPopup" noautohide="true">
<iframe id="customizeToolbarSheetIFrame"
style="&dialog.style;"
#ifdef MOZILLA_1_9_1_BRANCH
--- a/mail/base/content/messenger.xul
+++ b/mail/base/content/messenger.xul
@@ -200,17 +200,17 @@
onclick="gFolderTreeView.cycleMode(false);"/>
<toolbarbutton id="folderview-cycler-next"
chromedir="&locale.dir;"
dir="next"
class="folderview-cycler"
onclick="gFolderTreeView.cycleMode(true);"/>
</sidebarheader>
- <tree id="folderTree" class="plain focusring" flex="1"
+ <tree id="folderTree" class="plain" flex="1"
hidecolumnpicker="true" persist="mode" mode="all"
keepcurrentinview="true"
context="folderPaneContext"
disableKeyNavigation="true"
ondraggesture="gFolderTreeView._onDragStart(event);"
ondragover="gFolderTreeView._onDragOver(event);"
ondblclick="gFolderTreeView.onDoubleClick(event);"
onselect="FolderPaneSelectionChange();">
@@ -237,29 +237,34 @@
minheight="100" height="100" persist="height"
onselect="ObserveDisplayDeckChange(event)">
<!-- first panel in displayDeck is Account Central -->
<vbox id="accountCentralBox" flex="1">
<iframe name="accountCentralPane" width="150" flex="1" src="about:blank"/>
</vbox>
<!-- second panel is the threadPane -->
<hbox id="threadPaneBox">
+ <!-- The threadContentArea was specially created to be a place for
+ things that want to be above/below the thread pane, regardless
+ of where the message reader ("messagepane") gets off to. -->
+ <vbox id="threadContentArea" flex="1">
<tree id="threadTree"
persist="lastfoldersent width"
treelines="true"
flex="2"
enableColumnDrag="true"
_selectDelay="250"
- class="plain focusring"
+ class="plain"
lastfoldersent="false"
keepcurrentinview="true"
disableKeyNavigation="true"
context="mailContext"
onkeypress="ThreadPaneKeyPress(event);"
- onselect="ThreadPaneSelectionChanged();">
+ onselect="ThreadPaneSelectionChanged();"
+ >
<treecols id="threadCols" pickertooltiptext="&columnChooser.tooltip;">
<treecol id="threadCol" persist="hidden ordinal" fixed="true" cycler="true"
class="treecol-image threadColumnHeader" currentView="unthreaded"
label="&threadColumn.label;" tooltiptext="&threadColumn.tooltip;"/>
<splitter class="tree-splitter"/>
<treecol id="attachmentCol" persist="hidden ordinal" fixed="true"
class="treecol-image attachmentColumnHeader"
label="&attachmentColumn.label;" tooltiptext="&attachmentColumn.tooltip;"/>
@@ -317,17 +322,18 @@
<treecol id="locationCol" persist="width" flex="1" hidden="true" ignoreincolumnpicker="true"
label="&locationColumn.label;" tooltiptext="&locationColumn.tooltip;"/>
<splitter class="tree-splitter"/>
<treecol id="idCol" persist="hidden ordinal width" flex="1" hidden="true"
label="&idColumn.label;" tooltiptext="&idColumn.tooltip;"/>
</treecols>
<treechildren ondraggesture="threadPaneOnDragStart(event);"/>
</tree>
- </hbox>
+ </vbox>
+ </hbox>
<!-- extensions may overlay in additional panels; don't assume that there are only 2! -->
</deck> <!-- displayDeck -->
<!-- if you change this id, please change GetThreadAndMessagePaneSplitter() and MsgToggleMessagePane() -->
<splitter id="threadpane-splitter" collapse="after" persist="state" collapsed="true"
onmouseup="OnMouseUpThreadAndMessagePaneSplitter()"/>
<vbox id="messagepanebox" flex="2" minheight="100" height="200"
--- a/mail/base/content/msgHdrViewOverlay.js
+++ b/mail/base/content/msgHdrViewOverlay.js
@@ -1,48 +1,48 @@
-# -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is Mozilla Communicator client code, released
-# March 31, 1998.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 1998-1999
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# Markus Hossner <markushossner@gmx.de>
-# Mark Banner <bugzilla@standard8.plus.com>
-# David Ascher <dascher@mozillamessaging.com>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Markus Hossner <markushossner@gmx.de>
+ * Mark Banner <bugzilla@standard8.plus.com>
+ * David Ascher <dascher@mozillamessaging.com>
+ * Dan Mosedale <dmose@mozillamessagin.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
/* This is where functions related to displaying the headers for a selected message in the
message pane live. */
////////////////////////////////////////////////////////////////////////////////////
// Warning: if you go to modify any of these JS routines please get a code review from
// scott@scott-macgregor.org. It's critical that the code in here for displaying
@@ -51,23 +51,20 @@
// pane, we batch up all the changes for displaying the header pane (to, cc, attachements button, etc.)
// and we make a single pass to display them. It's critical that we maintain this one reflow per message
// view in the message header pane.
////////////////////////////////////////////////////////////////////////////////////
var gViewAllHeaders = false;
var gMinNumberOfHeaders = 0;
var gDummyHeaderIdIndex = 0;
-var gCollapsedHeaderViewMode = false;
var gBuildAttachmentsForCurrentMsg = false;
var gBuildAttachmentPopupForCurrentMsg = true;
var gBuiltExpandedView = false;
-var gBuiltCollapsedView = false;
var gMessengerBundle;
-var gProfileDirURL;
var gHeadersShowReferences = false;
var gShowCondensedEmailAddresses = true; // show the friendly display names for people I know instead of the name + email address
// other components may listen to on start header & on end header notifications for each message we display
// to do that you need to add yourself to our gMessageListeners array with an object that supports the three properties:
// onStartHeaders, onEndHeaders and onEndAttachments.
var gMessageListeners = new Array();
@@ -85,26 +82,17 @@ var gMessageListeners = new Array();
// short presentation we'll use the short one. i.e. if you are showing the From field and you
// set this to true, we can show just "John Doe" instead of "John Doe <jdoe@netscape.net>".
// (DEFAULT: false)
//
// outputFunction: this is a method which takes a headerEntry (see the definition below) and a header value
// This allows you to provide your own methods for actually determining how the header value
// is displayed. (DEFAULT: updateHeaderValue which just sets the header value on the text node)
-// Our first view is the collapsed view. This is very light weight view of the data. We only show a couple
-// fields.
-var gCollapsedHeaderList = [ {name:"subject", outputFunction:updateHeaderValueInTextNode},
- {name:"from", useToggle:true, useShortView:true, outputFunction:OutputEmailAddresses},
- {name:"toCcBcc", useToggle:true,
- useShortView:true,
- outputFunction: OutputEmailAddresses},
- {name:"date", outputFunction:OutputDate}];
-
-// We also have an expanded header view. This shows many of your more common (and useful) headers.
+// This expanded header view shows many of the more common (and useful) headers.
var gExpandedHeaderList = [ {name:"subject"},
{name:"from", useToggle:true, outputFunction:OutputEmailAddresses},
{name:"reply-to", useToggle:true, outputFunction:OutputEmailAddresses},
{name:"date"},
{name:"to", useToggle:true, outputFunction:OutputEmailAddresses},
{name:"cc", useToggle:true, outputFunction:OutputEmailAddresses},
{name:"bcc", useToggle:true, outputFunction:OutputEmailAddresses},
{name:"newsgroups", outputFunction:OutputNewsgroups},
@@ -116,17 +104,16 @@ var gExpandedHeaderList = [ {name:"subje
// These are all the items that use a mail-multi-emailHeaderField widget and
// therefore may require updating if the address book changes.
const gEmailAddressHeaderNames = ["from", "reply-to",
"to", "cc", "bcc", "toCcBcc"];
// Now, for each view the message pane can generate, we need a global table of headerEntries. These
// header entry objects are generated dynamically based on the static data in the header lists (see above)
// and elements we find in the DOM based on properties in the header lists.
-var gCollapsedHeaderView = {};
var gExpandedHeaderView = {};
// currentHeaderData --> this is an array of header name and value pairs for the currently displayed message.
// it's purely a data object and has no view information. View information is contained in the view objects.
// for a given entry in this array you can ask for:
// .headerName ---> name of the header (i.e. 'to'). Always stored in lower case
// .headerValue --> value of the header "johndoe@netscape.net"
var currentHeaderData = {};
@@ -142,17 +129,17 @@ var currentAttachments = new Array();
const nsIAbListener = Components.interfaces.nsIAbListener;
const nsIAbCard = Components.interfaces.nsIAbCard;
// createHeaderEntry --> our constructor method which creates a header Entry
// based on an entry in one of the header lists. A header entry is different from a header list.
// a header list just describes how you want a particular header to be presented. The header entry
// actually has knowledge about the DOM and the actual DOM elements associated with the header.
-// prefix --> the name of the view (i.e. "collapsed", "expanded")
+// prefix --> the name of the view (e.g. "expanded")
// headerListInfo --> entry from a header list.
function createHeaderEntry(prefix, headerListInfo)
{
var useShortView = false;
var partialIDName = prefix + headerListInfo.name;
this.enclosingBox = document.getElementById(partialIDName + 'Box');
this.textNode = document.getElementById(partialIDName + 'Value');
this.isNewHeader = false;
@@ -191,22 +178,16 @@ function createHeaderEntry(prefix, heade
function initializeHeaderViewTables()
{
var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch2);
// iterate over each header in our header list arrays and create header entries
// for each one. These header entries are then stored in the appropriate header table
var index;
- for (index = 0; index < gCollapsedHeaderList.length; index++)
- {
- gCollapsedHeaderView[gCollapsedHeaderList[index].name] =
- new createHeaderEntry('collapsed', gCollapsedHeaderList[index]);
- }
-
for (index = 0; index < gExpandedHeaderList.length; index++)
{
var headerName = gExpandedHeaderList[index].name;
gExpandedHeaderView[headerName] = new createHeaderEntry('expanded', gExpandedHeaderList[index]);
}
var extraHeaders = prefBranch.getCharPref("mailnews.headers.extraExpandedHeaders").split(' ');
for (index = 0; index < extraHeaders.length; index++)
@@ -259,26 +240,25 @@ function OnLoadMsgHeaderPane()
// Add an address book listener so we can update the header view when things
// change.
Components.classes["@mozilla.org/abmanager;1"]
.getService(Components.interfaces.nsIAbManager)
.addAddressBookListener(AddressBookListener,
Components.interfaces.nsIAbListener.all);
- var deckHeaderView = document.getElementById("msgHeaderViewDeck");
- gCollapsedHeaderViewMode = deckHeaderView.selectedIndex == 0;
-
- // work around XUL deck bug where collapsed header view, if it's the persisted
- // default, wouldn't be sized properly because of the larger expanded
- // view "stretches" the deck.
- if (gCollapsedHeaderViewMode)
- document.getElementById('expandedHeaderView').collapsed = true;
- else
- document.getElementById('collapsedHeaderView').collapsed = true;
+ // if an invalid index is selected; reset to 0. One way this can happen
+ // is if a value of 1 was persisted to localStore.rdf by Tb2 (when there were
+ // two panels), and then the user upgraded to Tb3, which only has one.
+ // Presumably this can also catch cases of extension uninstalls as well.
+ let deckElement = document.getElementById('msgHeaderViewDeck')
+ if (deckElement.selectedIndex < 0 ||
+ deckElement.selectedIndex >= deckElement.childElementCount) {
+ deckElement.selectedIndex = 0;
+ }
// dispatch an event letting any listeners know that we have loaded the message pane
var event = document.createEvent('Events');
event.initEvent('messagepane-loaded', false, true);
var headerViewElement = document.getElementById("msgHeaderView");
headerViewElement.dispatchEvent(event);
}
@@ -338,25 +318,16 @@ var AddressBookListener =
OnAddressBookDataChanged(nsIAbListener.itemChanged, null, aItem);
}
};
function OnAddressBookDataChanged(aAction, aParentDir, aItem) {
gEmailAddressHeaderNames.forEach(function (headerName) {
var headerEntry = null;
- // Ensure both collapsed and expanded are updated in case we toggle
- // between the two.
- if (headerName in gCollapsedHeaderView) {
- headerEntry = gCollapsedHeaderView[headerName];
- if (headerEntry)
- headerEntry.enclosingBox.updateExtraAddressProcessing(aAction,
- aParentDir,
- aItem);
- }
if (headerName in gExpandedHeaderView) {
headerEntry = gExpandedHeaderView[headerName];
if (headerEntry)
headerEntry.enclosingBox.updateExtraAddressProcessing(aAction,
aParentDir,
aItem);
}
});
@@ -386,41 +357,44 @@ var messageHeaderSink = {
initializeHeaderViewTables();
}
gViewAllHeaders = false;
}
ClearCurrentHeaders();
gBuiltExpandedView = false;
- gBuiltCollapsedView = false;
gBuildAttachmentsForCurrentMsg = false;
gBuildAttachmentPopupForCurrentMsg = true;
ClearAttachmentList();
ClearEditMessageBox();
gMessageNotificationBar.clearMsgNotifications();
- for (index in gMessageListeners)
+ for (let index in gMessageListeners)
gMessageListeners[index].onStartHeaders();
},
onEndHeaders: function()
{
ShowMessageHeaderPane();
// WARNING: This is the ONLY routine inside of the message Header Sink that should
// trigger a reflow!
- ClearHeaderView(gCollapsedHeaderView);
ClearHeaderView(gExpandedHeaderView);
EnsureSubjectValue(); // make sure there is a subject even if it's empty so we'll show the subject and the twisty
- UpdateMessageHeaders();
+ // Only update the expanded view if it's actually selected (an
+ // extension-provided panel could be visible instead) and needs updating.
+ if (document.getElementById('msgHeaderViewDeck').selectedIndex == 0 &&
+ !gBuiltExpandedView) {
+ UpdateExpandedMessageHeaders();
+ }
+
ShowEditMessageBox();
UpdateJunkButton();
- UpdateReplyButtons();
for (index in gMessageListeners)
gMessageListeners[index].onEndHeaders();
},
processHeaders: function(headerNameEnumerator, headerValueEnumerator, dontCollectAddress)
{
this.onStartHeaders();
@@ -498,22 +472,18 @@ var messageHeaderSink = {
this.onEndHeaders();
},
handleAttachment: function(contentType, url, displayName, uri, isExternalAttachment)
{
// presentation level change....don't show vcards as external attachments in the UI.
// libmime already renders them inline.
- try
- {
- if (!this.mSaveHdr)
- this.mSaveHdr = messenger.messageServiceFromURI(uri).messageURIToMsgHdr(uri);
- }
- catch (ex) {}
+ if (!this.mSaveHdr)
+ this.mSaveHdr = messenger.messageServiceFromURI(uri).messageURIToMsgHdr(uri);
if (contentType == "text/x-vcard")
{
var inlineAttachments = pref.getBoolPref("mail.inline_attachments");
var displayHtmlAs = pref.getIntPref("mailnews.display.html_as");
if (inlineAttachments && !displayHtmlAs)
{
return;
}
@@ -526,48 +496,41 @@ var messageHeaderSink = {
// We only need to do this on the first attachment.
var numAttachments = currentAttachments.length;
if (numAttachments == 1) {
// we also have to enable the File/Attachments menuitem
var node = document.getElementById("fileAttachmentMenu");
if (node)
node.removeAttribute("disabled");
- try {
- // convert the uri into a hdr
- this.mSaveHdr.markHasAttachments(true);
- }
- catch (ex) {
- dump("ex = " + ex + "\n");
- }
+ // convert the uri into a hdr
+ this.mSaveHdr.markHasAttachments(true);
}
},
onEndAllAttachments: function()
{
displayAttachmentsForExpandedView();
+ gMessageDisplay.messageLoading = false;
+ gMessageDisplay.messageLoaded = true;
+
for (index in gMessageListeners) {
if ("onEndAttachments" in gMessageListeners[index])
gMessageListeners[index].onEndAttachments();
}
},
onEndMsgDownload: function(url)
{
// if we don't have any attachments, turn off the attachments flag
if (!this.mSaveHdr)
{
var messageUrl = url.QueryInterface(Components.interfaces.nsIMsgMessageUrl);
- try
- {
- this.mSaveHdr = messenger.msgHdrFromURI(messageUrl.uri);
- }
- catch (ex) {}
-
+ this.mSaveHdr = messenger.msgHdrFromURI(messageUrl.uri);
}
if (!currentAttachments.length && this.mSaveHdr)
this.mSaveHdr.markHasAttachments(false);
OnMsgParsed(url);
},
onEndMsgHeaders: function(url)
{
@@ -591,40 +554,39 @@ var messageHeaderSink = {
},
mDummyMsgHeader: null,
get dummyMsgHeader()
{
if (!this.mDummyMsgHeader)
this.mDummyMsgHeader = new nsDummyMsgHeader();
+ // The URI resolution will never work on the dummy header;
+ // save it now... we know it will be needed eventually.
+ // (And save it every time we come through here, not just when
+ // we create it; the onStartHeaders might come after creation!)
+ this.mSaveHdr = this.mDummyMsgHeader;
return this.mDummyMsgHeader;
},
mProperties: null,
get properties()
{
if (!this.mProperties)
this.mProperties = Components.classes["@mozilla.org/hash-property-bag;1"].
createInstance(Components.interfaces.nsIWritablePropertyBag2);
return this.mProperties;
}
};
function SetTagHeader()
{
// it would be nice if we passed in the msgHdr from the back end
- var msgHdr;
- try
- {
- msgHdr = gDBView.hdrForFirstSelectedMessage;
- }
- catch (ex)
- {
+ var msgHdr = gFolderDisplay.selectedMessage;
+ if (!msgHdr)
return; // no msgHdr to add our tags to
- }
// get the list of known tags
var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
.getService(Components.interfaces.nsIMsgTagService);
var tagArray = tagService.getAllTags({});
var tagKeys = {};
for each (var tagInfo in tagArray)
if (tagInfo.tag)
@@ -678,20 +640,18 @@ function OnTagsChange()
{
var headerEntry = gExpandedHeaderView.tags;
if (headerEntry)
{
headerEntry.valid = ("tags" in currentHeaderData);
if (headerEntry.valid)
headerEntry.outputFunction(headerEntry, currentHeaderData.tags.headerValue);
- // if we are showing the expanded header view then we may need to collapse or
- // show the tag header box...
- if (!gCollapsedHeaderViewMode)
- headerEntry.enclosingBox.collapsed = !headerEntry.valid;
+ // we may need to collapse or show the tag header box...
+ headerEntry.enclosingBox.collapsed = !headerEntry.valid;
}
}
}
// flush out any local state being held by a header entry for a given
// table
function ClearHeaderView(headerTable)
{
@@ -771,60 +731,39 @@ function EnsureMinimumNumberOfHeaders (h
gDummyHeaderIdIndex++;
numEmptyHeaders--;
}
}
}
-// make sure the appropriate fields within the currently displayed view header mode
-// are collapsed or visible...
-function updateHeaderViews()
+// make sure the appropriate fields in the expanded header view are collapsed
+// or visible...
+function updateExpandedView()
{
- if (gCollapsedHeaderViewMode)
- showHeaderView(gCollapsedHeaderView);
- else
- {
- if (gMinNumberOfHeaders)
+ // if the expanded view isn't selected, don't bother updating it
+ if (document.getElementById('msgHeaderViewDeck').selectedIndex != 0)
+ return;
+
+ if (gMinNumberOfHeaders)
EnsureMinimumNumberOfHeaders(gExpandedHeaderView);
- showHeaderView(gExpandedHeaderView);
- }
+ showHeaderView(gExpandedHeaderView);
+
UpdateJunkButton();
UpdateReplyButtons();
displayAttachmentsForExpandedView();
}
-function ToggleHeaderView ()
-{
- gCollapsedHeaderViewMode = !gCollapsedHeaderViewMode;
- // Work around a xul deck bug where the height of the deck is determined by the tallest panel in the deck
- // even if that panel is not selected...
- document.getElementById('msgHeaderViewDeck').selectedPanel.collapsed = true;
- UpdateMessageHeaders();
-
- // select the new panel.
- document.getElementById('msgHeaderViewDeck').selectedIndex = gCollapsedHeaderViewMode ? 0 : 1;
-
- // Work around a xul deck bug where the height of the deck is determined by the tallest panel in the deck
- // even if that panel is not selected...
- document.getElementById('msgHeaderViewDeck').selectedPanel.collapsed = false;
-}
-
// default method for updating a header value into a header entry
function updateHeaderValue(headerEntry, headerValue)
{
headerEntry.enclosingBox.headerValue = headerValue;
}
-function updateHeaderValueInTextNode(headerEntry, headerValue)
-{
- headerEntry.textNode.value = headerValue;
-}
-
function createNewHeaderView(headerName, label)
{
var idName = 'expanded' + headerName + 'Box';
var newHeader = document.createElement("mail-headerfield");
newHeader.setAttribute('id', idName);
newHeader.setAttribute('label', label);
newHeader.setAttribute('flex', '1');
@@ -853,136 +792,114 @@ function removeNewHeaderViews(aHeaderTab
{
var headerEntry = aHeaderTable[index];
if (headerEntry.isNewHeader)
headerEntry.enclosingBox.parentNode
.removeChild(headerEntry.enclosingBox);
}
}
-// UpdateMessageHeaders: Iterate through all the current header data we received from mime for this message
-// for each header entry table, see if we have a corresponding entry for that header. i.e. does the particular
-// view care about this header value. if it does then call updateHeaderEntry
-function UpdateMessageHeaders()
-{
+// UpdateExpandedMessageHeaders: Iterate through all the current header data
+// we received from mime for this message for the expanded header entry table,
+// and see if we have a corresponding entry for that header (i.e.
+// whether the expanded header view cares about this header value)
+// If so, then call updateHeaderEntry
+function UpdateExpandedMessageHeaders() {
// iterate over each header we received and see if we have a matching entry in each
// header view table...
-
var headerName;
// Remove the height attr so that it redraws correctly. Works around a problem that
// attachment-splitter causes if it's moved high enough to affect the header box:
document.getElementById('msgHeaderView').removeAttribute('height');
- for (headerName in currentHeaderData)
- {
+ for (headerName in currentHeaderData) {
var headerField = currentHeaderData[headerName];
var headerEntry = null;
- if (headerName == "subject")
- {
- try {
- if (gDBView.keyForFirstSelectedMessage == nsMsgKey_None)
- {
- var folder = null;
- if (gCurrentFolderUri)
- folder = GetMsgFolderFromUri(gCurrentFolderUri);
- setTitleFromFolder(folder, headerField.headerValue);
- }
- } catch (ex) {}
+ if (headerName in gExpandedHeaderView)
+ headerEntry = gExpandedHeaderView[headerName];
+
+ if (!headerEntry && gViewAllHeaders) {
+ // for view all headers, if we don't have a header field for this
+ // value....cheat and create one....then fill in a headerEntry
+ if (headerName == "message-id" || headerName == "in-reply-to") {
+ var messageIdEntry = {
+ name: headerName,
+ outputFunction: OutputMessageIds
+ };
+ gExpandedHeaderView[headerName] = new createHeaderEntry('expanded',
+ messageIdEntry);
+ }
+ else {
+ gExpandedHeaderView[headerName] =
+ new createNewHeaderView(headerName,
+ currentHeaderData[headerName].headerName);
+ }
+
+ headerEntry = gExpandedHeaderView[headerName];
}
- if (gCollapsedHeaderViewMode && !gBuiltCollapsedView)
- {
- if (headerName == "cc" || headerName == "to" || headerName == "bcc")
- headerEntry = gCollapsedHeaderView["toCcBcc"];
- else if (headerName in gCollapsedHeaderView)
- headerEntry = gCollapsedHeaderView[headerName];
- }
- else if (!gCollapsedHeaderViewMode && !gBuiltExpandedView)
- {
- if (headerName in gExpandedHeaderView)
- headerEntry = gExpandedHeaderView[headerName];
-
- if (!headerEntry && gViewAllHeaders)
- {
- // for view all headers, if we don't have a header field for this value....cheat and create one....then
- // fill in a headerEntry
- if (headerName == "message-id" || headerName == "in-reply-to")
- {
- var messageIdEntry = {name:headerName, outputFunction:OutputMessageIds};
- gExpandedHeaderView[headerName] = new createHeaderEntry('expanded', messageIdEntry);
- }
- else
- {
- gExpandedHeaderView[headerName] =
- new createNewHeaderView(headerName,
- currentHeaderData[headerName].headerName);
- }
-
- headerEntry = gExpandedHeaderView[headerName];
- }
- } // if we are in expanded view....
-
- if (headerEntry)
- {
+ if (headerEntry) {
if (headerName == "references" &&
!(gViewAllHeaders || gHeadersShowReferences ||
- (gDBView.msgFolder && gDBView.msgFolder.server.type == "nntp")))
- {
- // hide references header if view all headers mode isn't selected, the pref show references is
- // deactivated and the currently displayed message isn't a newsgroup posting
+ gFolderDisplay.view.isNewsFolder)) {
+ // hide references header if view all headers mode isn't selected, the
+ // pref show references is deactivated and the currently displayed
+ // message isn't a newsgroup posting
headerEntry.valid = false;
}
else
{
headerEntry.outputFunction(headerEntry, headerField.headerValue);
headerEntry.valid = true;
}
}
}
- if (gCollapsedHeaderViewMode)
- gBuiltCollapsedView = true;
- else
- gBuiltExpandedView = true;
+ gBuiltExpandedView = true;
// now update the view to make sure the right elements are visible
- updateHeaderViews();
+ updateExpandedView();
}
function ClearCurrentHeaders()
{
currentHeaderData = {};
currentAttachments = new Array();
}
function ShowMessageHeaderPane()
{
document.getElementById('msgHeaderView').collapsed = false;
- /* workaround for 39655 */
- if (gFolderJustSwitched)
- {
- var el = document.getElementById("msgHeaderView");
- el.setAttribute("style", el.getAttribute("style"));
- gFolderJustSwitched = false;
- }
+ // We used to do this as a work-around for long-ago bug 39655
+ // there apparently was a layout bug where the message pane
+ // 'toolbar' was being hidden as a result of the folder change,
+ // then re-shown, but the layout would glitch and not show it.
+ // As much as I love cargo-culting, I am commenting this out
+ // because I have great respect for our layout ninjas and little
+ // respect for random global variables such as the one that
+ // controlled this.
+ //
+ //var el = document.getElementById("msgHeaderView");
+ //el.setAttribute("style", el.getAttribute("style"));
+ //
}
function HideMessageHeaderPane()
{
document.getElementById('msgHeaderView').collapsed = true;
// disable the File/Attachments menuitem
document.getElementById("fileAttachmentMenu").setAttribute("disabled", "true");
// disable the attachment box
document.getElementById("attachmentView").collapsed = true;
document.getElementById("attachment-splitter").collapsed = true;
-
+
ClearEditMessageBox();
}
function OutputNewsgroups(headerEntry, headerValue)
{
headerValue = headerValue.replace(/,/g,", ");
updateHeaderValue(headerEntry, headerValue);
}
@@ -1108,22 +1025,22 @@ function UpdateEmailNodeDetails(aEmailAd
getCardForEmail(aEmailAddress);
var displayName = null;
aDocumentNode.cardDetails = cardDetails;
if (cardDetails.card) {
displayName = cardDetails.card.displayName;
aDocumentNode.setAttribute("hascard", "true");
- aDocumentNode.setAttribute("tooltipstar",
+ aDocumentNode.setAttribute("tooltipstar",
document.getElementById("editContactItem").label);
}
else {
aDocumentNode.setAttribute("hascard", "false");
- aDocumentNode.setAttribute("tooltipstar",
+ aDocumentNode.setAttribute("tooltipstar",
document.getElementById("addToAddressBookItem").label);
}
// When we are adding cards, we don't want to move the display around if the
// user has clicked on the star, therefore if it is locked, just exit and
// leave the display updates until later.
if (aDocumentNode.hasAttribute("updatingUI"))
return;
@@ -1205,17 +1122,17 @@ function UpdateExtraAddressProcessing(aA
function findEmailNodeFromPopupNode(elt, popup)
{
// This annoying little function is needed because in the binding for
// mail-emailaddress, we set the context on the <description>, but that if
// the user clicks on the label, then popupNode is set to it, rather than
// the description. So we have walk up the parent until we find the
// element with the popup set, and then return its parent.
-
+
while (elt.getAttribute("popup") != popup)
{
elt = elt.parentNode;
if (elt == null)
return null;
}
return elt.parentNode;
}
@@ -1292,16 +1209,29 @@ function onClickEmailStar(event, emailAd
if (emailAddressNode && emailAddressNode.cardDetails &&
emailAddressNode.cardDetails.card)
EditContact(emailAddressNode);
else
AddContact(emailAddressNode);
}
+/**
+ * @return the DOM node for the header-view-button-box in the currently
+ * selected panel of the message header
+ *
+ * @note that this assumes that the first such button box is the only one
+ * worth caring about (having more than one per panel is not supported).
+ */
+function getCurrentMsgHdrButtonBox() {
+
+ return document.getElementById('msgHeaderViewDeck').selectedPanel
+ .getElementsByTagName("header-view-button-box").item(0);
+}
+
function AddContact(emailAddressNode)
{
if (emailAddressNode) {
// When we collect an address, it updates the AB which sends out
// notifications to update the UI. In the add case we don't want to update
// the UI so that accidentally double-clicking on the star doesn't lead
// to something strange (i.e star would be moved out from underneath,
// leaving something else there).
@@ -1412,18 +1342,18 @@ function detachAttachment(aAttachment, a
}
/**
* Return true if possible attachments in the currently loaded message can be
* deleted/detached.
*/
function CanDetachAttachments()
{
- var uri = GetLoadedMessage();
- var canDetach = !IsNewsMessage(uri) && (!IsImapMessage(uri) || MailOfflineMgr.isOnline());
+ var canDetach = !gFolderDisplay.selectedMessageIsNews &&
+ (!gFolderDisplay.selectedMessageIsImap || MailOfflineMgr.isOnline());
if (canDetach && ("content-type" in currentHeaderData))
canDetach = !ContentTypeIsSMIME(currentHeaderData["content-type"].headerValue);
return canDetach;
}
/** Return true if the content type is an S/MIME one. */
function ContentTypeIsSMIME(contentType)
{
@@ -1860,25 +1790,19 @@ function ClearAttachmentList()
while (list.hasChildNodes())
list.removeChild(list.lastChild);
}
function ShowEditMessageBox()
{
// it would be nice if we passed in the msgHdr from the back end
- var msgHdr;
- try
- {
- msgHdr = gDBView.hdrForFirstSelectedMessage;
- }
- catch (ex)
- {
+ var msgHdr = gFolderDisplay.selectedMessage;
+ if (!msgHdr && !msgHdr.folder)
return;
- }
const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
if (IsSpecialFolder(msgHdr.folder, nsMsgFolderFlags.Drafts, true))
document.getElementById("editMessageBox").collapsed = false;
}
function ClearEditMessageBox()
{
var editBox = document.getElementById("editMessageBox");
@@ -1987,22 +1911,39 @@ nsFlavorDataProvider.prototype =
function nsDummyMsgHeader()
{
}
nsDummyMsgHeader.prototype =
{
mProperties : new Array,
- getStringProperty : function(property) {return this.mProperties[property];},
- setStringProperty : function(property, val) {this.mProperties[property] = val;},
+ getStringProperty : function(aProperty) {
+ if (aProperty in this.mProperties)
+ return this.mProperties[property];
+ return "";
+ },
+ setStringProperty : function(aProperty, aVal) {
+ this.mProperties[aProperty] = aVal;
+ },
+ getUint32Property : function(aProperty) {
+ if (aProperty in this.mProperties)
+ return parseInt(this.mProperties[aProperty]);
+ return 0;
+ },
+ setUint32Property: function(aProperty, aVal) {
+ this.mProperties[aProperty] = aVal.toString();
+ },
markHasAttachments : function(hasAttachments) {},
messageSize : 0,
recipients : null,
from : null,
subject : null,
+ get mime2DecodedSubject() { return this.subject; },
ccList : null,
listPost : null,
messageId : null,
accountKey : "",
+ // if you change us to return a fake folder, please update
+ // folderDisplay.js's FolderDisplayWidget's selectedMessageIsExternal getter.
folder : null
};
--- a/mail/base/content/msgHdrViewOverlay.xul
+++ b/mail/base/content/msgHdrViewOverlay.xul
@@ -92,54 +92,19 @@
accesskey="&deleteAllAttachmentsCmd.accesskey;" oncommand="HandleAllAttachments('delete');" />
</popup>
<popup id="copyUrlPopup" popupanchor="bottomleft">
<menuitem label="©LinkCmd.label;" accesskey="©LinkCmd.accesskey;" oncommand="CopyWebsiteAddress(document.popupNode)"/>
</popup>
<hbox id="msgHeaderView" collapsed="true" class="main-header-area">
- <deck id="msgHeaderViewDeck" persist="selectedIndex" selectedIndex="1" flex="1">
- <!-- the message pane consists of 4 'boxes'. Box #1 is a grid, showing a brief view of the headers -->
- <vbox id="collapsedHeaderView" class="header-part1 headerContainer" flex="1" pack="start">
- <hbox align="start">
- <hbox id="collapsedfromBox" align="center" flex="1">
- <mail-multi-emailHeaderField id="collapsedfromValue"
- class="collapsedHeaderDisplayName"
- label="&fromField2.label;"/>
- </hbox>
- <header-view-button-box id="collapsedButtonBox"/>
- </hbox>
-
- <hbox align="baseline">
- <hbox id="collapsedsubjectBox" flex="1">
- <textbox id="collapsedsubjectValue" flex="1" readonly="true"
- class="collapsedHeaderValue plain"/>
- </hbox>
- <hbox id="collapseddateBox" flex="0">
- <textbox id="collapseddateValue" class="plain collapsedHeaderValue"
- flex="0" readonly="true"/>
- </hbox>
- </hbox>
-
- <hbox flex="1" align="center">
- <hbox id="collapsedtoCcBccBox"
- align="center" flex="1">
- <mail-multi-emailHeaderField id="collapsedtoCcBccValue"
- flex="1"
- align="baseline"
- class="collapsedHeaderDisplayName"/>
- </hbox>
- <button id="showDetailsButton"
- tooltiptext="&showDetailsButton.label;"
- onclick="ToggleHeaderView();"
- class="msgHeaderView-button msgHeaderView-flat-button"/>
- </hbox>
- </vbox>
-
+ <deck id="msgHeaderViewDeck" selectedIndex="0"
+ persist="selectedIndex" flex="1">
+
<!-- the message pane consists of 3 'boxes'. Box #2 is the expanded headers view (the default view) -->
<hbox id="expandedHeaderView" flex="1">
<!-- XXX this should switch to using <grid> for expandedHeaders to get
away from gross historical CSS pseudo-boxery used for header
name positioning -->
<vbox id="expandedHeaders" flex="1">
@@ -212,29 +177,26 @@
<mail-urlfield id="expandedcontent-baseBox"
label="&originalWebsite2.label;" collapsed="true"/>
<mail-headerfield id="expandeduser-agentBox"
label="&userAgentField2.label;"
collapsed="true"/>
</vbox>
<!-- perhaps this should be a customizable toolbar too -->
- <vbox id="hideDetailsBox" align="end">
- <spacer flex="1"/>
- <button id="hideDetailsButton" label="&hideDetailsButton.label;"
- onclick="ToggleHeaderView();"
- class="msgHeaderView-button msgHeaderView-flat-button"/>
+ <vbox id="otherActionsBox" align="end">
+ <spacer id="otherActionsTopSpacer" flex="1"/>
<button type="menu" id="otherActionsButton"
label="&otherActionsButton.label;"
class="msgHeaderView-button msgHeaderView-flat-button">
<menupopup id="otherActionsPopup">
<menuitem id="viewSourceMenuItem"
label="&viewSourceMenuItem.label;"
accesskey="&viewSourceMenuItem.accesskey;"
- oncommand="ViewPageSource(GetSelectedMessages());" />
+ oncommand="ViewPageSource(gFolderDisplay.selectedMessageUris);" />
<menuitem id="saveAsMenuItem" label="&saveAsMenuItem.label;"
accesskey="&saveAsMenuItem.accesskey;"
oncommand="MsgSaveAsFile();" />
</menupopup>
</button>
</vbox>
</hbox>
</vbox>
--- a/mail/base/content/msgMail3PaneWindow.js
+++ b/mail/base/content/msgMail3PaneWindow.js
@@ -1,167 +1,102 @@
-# -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is Mozilla Communicator client code, released
-# March 31, 1998.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 1998-1999
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# Jan Varga (varga@nixcorp.com)
-# HÃ¥kan Waara (hwaara@chello.se)
-# Neil Rashbrook (neil@parkwaycc.co.uk)
-# Seth Spitzer <sspitzer@netscape.com>
-# David Bienvenu <bienvenu@nventure.com>
-# Jeremy Morton <bugzilla@game-point.net>
-# Steffen Wilberg <steffen.wilberg@web.de>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
+/** ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Jan Varga (varga@nixcorp.com)
+ * HÃ¥kan Waara (hwaara@chello.se)
+ * Neil Rashbrook (neil@parkwaycc.co.uk)
+ * Seth Spitzer <sspitzer@netscape.com>
+ * David Bienvenu <bienvenu@nventure.com>
+ * Jeremy Morton <bugzilla@game-point.net>
+ * Steffen Wilberg <steffen.wilberg@web.de>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
Components.utils.import("resource://gre/modules/folderUtils.jsm");
Components.utils.import("resource://app/modules/activity/activityModules.js");
+Components.utils.import("resource://app/modules/jsTreeSelection.js");
/* This is where functions related to the 3 pane window are kept */
// from MailNewsTypes.h
const nsMsgKey_None = 0xFFFFFFFF;
const nsMsgViewIndex_None = 0xFFFFFFFF;
const kMailCheckOncePrefName = "mail.startup.enabledMailCheckOnce";
const kStandardPaneConfig = 0;
const kWidePaneConfig = 1;
const kVerticalPaneConfig = 2;
const kNumFolderViews = 4; // total number of folder views
+/** widget with id=messagepanebox, initialized by GetMessagePane() */
var gMessagePane;
+/** widget with id=searchInput, initialized by GetSearchInput() */
var gSearchInput;
var gThreadAndMessagePaneSplitter = null;
+/** widget with id=unreadMessageCount, initialized by GetUnreadCountElement() */
var gUnreadCount = null;
+/** widget with id=totalMessageCount, initialized by GetTotalCountElement() */
var gTotalCount = null;
var gCurrentFolderView;
-var gCurrentLoadingFolderURI;
-var gCurrentFolderToReroot;
-var gCurrentLoadingFolderSortType = 0;
-var gCurrentLoadingFolderSortOrder = 0;
-var gCurrentLoadingFolderViewType = 0;
-var gCurrentLoadingFolderViewFlags = 0;
-var gRerootOnFolderLoad = false;
-var gNextMessageAfterDelete = null;
-var gNextMessageAfterLoad = null;
-var gNextMessageViewIndexAfterDelete = -2;
-var gSelectedIndexWhenDeleting = -1;
-var gCurrentlyDisplayedMessage=nsMsgViewIndex_None;
var gStartFolderUri = null;
-var gStartMsgKey = nsMsgKey_None;
-var gSearchEmailAddress = null;
-var gRightMouseButtonDown = false;
-// Global var to keep track of which row in the thread pane has been selected
-// This is used to make sure that the row with the currentIndex has the selection
-// after a Delete or Move of a message that has a row index less than currentIndex.
-var gThreadPaneCurrentSelectedIndex = -1;
+/**
+ * Tracks whether the right mouse button changed the selection or not. If the
+ * user right clicks on the selection, it stays the same. If they click outside
+ * of it, we alter the selection (but not the current index) to be the row they
+ * clicked on.
+ *
+ * The value of this variable is an object with "view" and "selection" keys
+ * and values. The view value is the view whose selection we saved off, and
+ * the selection value is the selection object we saved off.
+ */
+var gRightMouseButtonSavedSelection = null;
var gLoadStartFolder = true;
var gNewAccountToLoad = null;
// Global var to keep track of if the 'Delete Message' or 'Move To' thread pane
// context menu item was triggered. This helps prevent the tree view from
// not updating on one of those menu item commands.
var gThreadPaneDeleteOrMoveOccurred = false;
-//If we've loaded a message, set to true. Helps us keep the start page around.
-var gHaveLoadedMessage;
-
var gDisplayStartupPage = false;
-function SelectAndScrollToKey(aMsgKey)
-{
- // select the desired message
- // if the key isn't found, we won't select anything
- gDBView.selectMsgByKey(aMsgKey);
-
- // is there a selection?
- // if not, bail out.
- var indicies = GetSelectedIndices(gDBView);
- if (!indicies || !indicies.length)
- return false;
-
- // now scroll to it
- EnsureRowInThreadTreeIsVisible(indicies[0]);
- return true;
-}
-
-// A helper routine called after a folder is loaded to make sure
-// we select and scroll to the correct message (could be the first new message,
-// could be the last displayed message, etc.)
-function ScrollToMessageAfterFolderLoad(folder)
-{
- var scrolled = gPrefBranch.getBoolPref("mailnews.scroll_to_new_message") &&
- ScrollToMessage(nsMsgNavigationType.firstNew, true, false /* selectMessage */);
- if (!scrolled && folder && gPrefBranch.getBoolPref("mailnews.remember_selected_message"))
- {
- // If we failed to scroll to a new message,
- // reselect the last selected message
- var lastMessageLoaded = folder.lastMessageLoaded;
- if (lastMessageLoaded != nsMsgKey_None)
- scrolled = SelectAndScrollToKey(lastMessageLoaded);
- }
-
- if (!scrolled)
- {
- // if we still haven't scrolled,
- // scroll to the newest, which might be the top or the bottom
- // depending on our sort order and sort type
- if (gDBView.sortOrder == nsMsgViewSortOrder.ascending)
- {
- switch (gDBView.sortType)
- {
- case nsMsgViewSortType.byDate:
- case nsMsgViewSortType.byReceived:
- case nsMsgViewSortType.byId:
- case nsMsgViewSortType.byThread:
- scrolled = ScrollToMessage(nsMsgNavigationType.lastMessage, true, false /* selectMessage */);
- break;
- }
- }
-
- // if still we haven't scrolled,
- // scroll to the top.
- if (!scrolled)
- EnsureRowInThreadTreeIsVisible(0);
- }
-}
-
// the folderListener object
var folderListener = {
OnItemAdded: function(parentItem, item) { },
OnItemRemoved: function(parentItem, item) { },
OnItemPropertyChanged: function(item, property, oldValue, newValue) { },
@@ -175,131 +110,17 @@ var folderListener = {
OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) { },
OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue) { },
OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) { },
OnItemEvent: function(folder, event) {
var eventType = event.toString();
- if (eventType == "FolderLoaded") {
- if (folder) {
- var scrolled = false;
- var msgFolder = folder.QueryInterface(Components.interfaces.nsIMsgFolder);
- var uri = folder.URI;
- var rerootingFolder = (uri == gCurrentFolderToReroot);
- if (rerootingFolder) {
- viewDebug("uri = gCurrentFolderToReroot, setting gQSViewIsDirty\n");
- gQSViewIsDirty = true;
- gCurrentFolderToReroot = null;
- if (msgFolder) {
- msgFolder.endFolderLoading();
- UpdateStatusQuota(msgFolder);
- // Suppress command updating when rerooting the folder.
- // When rerooting, we'll be clearing the selection
- // which will cause us to update commands.
- if (gDBView) {
- gDBView.suppressCommandUpdating = true;
- // If the db's view isn't set, something went wrong and we
- // should reroot the folder, which will re-open the view.
- if (!gDBView.db)
- gRerootOnFolderLoad = true;
- }
- if (gRerootOnFolderLoad)
- RerootFolder(uri, msgFolder, gCurrentLoadingFolderViewType, gCurrentLoadingFolderViewFlags, gCurrentLoadingFolderSortType, gCurrentLoadingFolderSortOrder);
-
- var db = msgFolder.msgDatabase;
- if (db)
- db.resetHdrCacheSize(100);
-
- if (gDBView) {
- gDBView.suppressCommandUpdating = false;
- }
-
- gCurrentLoadingFolderSortType = 0;
- gCurrentLoadingFolderSortOrder = 0;
- gCurrentLoadingFolderViewType = 0;
- gCurrentLoadingFolderViewFlags = 0;
-
- // Used for rename folder msg loading after folder is loaded.
- scrolled = LoadCurrentlyDisplayedMessage();
-
- if (gStartMsgKey != nsMsgKey_None) {
- scrolled = SelectAndScrollToKey(gStartMsgKey);
- gStartMsgKey = nsMsgKey_None;
- }
-
- if (gNextMessageAfterLoad) {
- var type = gNextMessageAfterLoad;
- gNextMessageAfterLoad = null;
-
- // Scroll to and select the proper message.
- scrolled = ScrollToMessage(type, true, true /* selectMessage */);
- }
- }
- }
- const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
- if (uri == gCurrentLoadingFolderURI) {
- viewDebug("uri == current loading folder uri\n");
- gCurrentLoadingFolderURI = "";
- // Scroll to message for virtual folders is done in
- // gSearchNotificationListener.OnSearchDone (see searchBar.js).
- if (!scrolled && gMsgFolderSelected &&
- !(gMsgFolderSelected.flags & nsMsgFolderFlags.Virtual))
- ScrollToMessageAfterFolderLoad(msgFolder);
- SetBusyCursor(window, false);
- }
- // Folder loading is over,
- // now issue quick search if there is an email address.
- viewDebug("in folder loaded gVirtualFolderTerms = " + gVirtualFolderTerms + "\n");
- viewDebug("in folder loaded gMsgFolderSelected = " + gMsgFolderSelected.URI + "\n");
- if (rerootingFolder)
- {
- if (gSearchEmailAddress)
- {
- Search(gSearchEmailAddress);
- gSearchEmailAddress = null;
- }
- else if (gVirtualFolderTerms)
- {
- gDefaultSearchViewTerms = null;
- viewDebug("searching gVirtualFolderTerms\n");
- gDBView.viewFolder = gMsgFolderSelected;
- ViewChangeByFolder(gMsgFolderSelected);
- }
- else if (gMsgFolderSelected.flags & nsMsgFolderFlags.Virtual)
- {
- viewDebug("selected folder is virtual\n");
- gDefaultSearchViewTerms = null;
- }
- else
- {
- // Get the view value from the folder.
- if (msgFolder)
- {
- // If our new view is the same as the old view and we already
- // have the list of search terms built up for the old view,
- // just re-use it.
- var result = GetMailViewForFolder(msgFolder);
- if (GetSearchInput() && gCurrentViewValue == result && gDefaultSearchViewTerms)
- {
- viewDebug("searching gDefaultSearchViewTerms and rerootingFolder\n");
- Search("");
- }
- else if (document.getElementById("mailviews-container")) // Only load the folder view if the views toolbar is visible.
- {
- viewDebug("changing view by value\n");
- ViewChangeByValue(result);
- }
- }
- }
- }
- }
- }
- else if (eventType == "ImapHdrDownloaded") {
+ if (eventType == "ImapHdrDownloaded") {
if (folder) {
var imapFolder = folder.QueryInterface(Components.interfaces.nsIMsgImapMailFolder);
if (imapFolder) {
var hdrParser = imapFolder.hdrParser;
if (hdrParser) {
var msgHdr = hdrParser.GetNewMsgHdr();
if (msgHdr)
{
@@ -311,232 +132,22 @@ var folderListener = {
if (hdrs && hdrs.indexOf("X-image-size:") > 0) {
msgHdr.setStringProperty("imageSize", "1");
}
}
}
}
}
}
- else if (eventType == "DeleteOrMoveMsgCompleted") {
- HandleDeleteOrMoveMsgCompleted(folder);
- }
- else if (eventType == "DeleteOrMoveMsgFailed") {
- HandleDeleteOrMoveMsgFailed(folder);
- }
- else if (eventType == "AboutToCompact") {
- if (gDBView)
- gCurrentlyDisplayedMessage = gDBView.currentlyDisplayedMessage;
- }
- else if (eventType == "CompactCompleted") {
- HandleCompactCompleted(folder);
- }
- else if (eventType == "RenameCompleted") {
- gFolderTreeView.selectFolder(folder);
- }
else if (eventType == "JunkStatusChanged") {
HandleJunkStatusChanged(folder);
}
}
}
-function HandleDeleteOrMoveMsgFailed(folder)
-{
- gDBView.onDeleteCompleted(false);
- if(IsCurrentLoadedFolder(folder)) {
- if(gNextMessageAfterDelete) {
- gNextMessageAfterDelete = null;
- gNextMessageViewIndexAfterDelete = -2;
- }
- }
-
- // fix me???
- // ThreadPaneSelectionChange(true);
-}
-
-// WARNING
-// this is a fragile and complicated function.
-// be careful when hacking on it.
-// Don't forget about things like different imap
-// delete models, multiple views (from multiple thread panes,
-// search windows, stand alone message windows)
-function HandleDeleteOrMoveMsgCompleted(folder)
-{
- // you might not have a db view. this can happen if
- // biff fires when the 3 pane is set to account central.
- if (!gDBView)
- return;
-
- gDBView.onDeleteCompleted(true);
-
- if (!IsCurrentLoadedFolder(folder)) {
- // default value after delete/move/copy is over
- gNextMessageViewIndexAfterDelete = -2;
- return;
- }
-
- var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
- var treeSelection = treeView.selection;
-
- if (gNextMessageViewIndexAfterDelete == -2) {
- // a move or delete can cause our selection can change underneath us.
- // this can happen when the user
- // deletes message from the stand alone msg window
- // or the search view, or another 3 pane
- if (treeSelection.count == 0) {
- // this can happen if you double clicked a message
- // in the thread pane, and deleted it from the stand alone msg window
- // see bug #172392
- treeSelection.clearSelection();
- setTitleFromFolder(folder, null);
- ClearMessagePane();
- UpdateMailToolbar("delete from another view, 0 rows now selected");
- }
- else if (treeSelection.count == 1) {
- // this can happen if you had two messages selected
- // in the thread pane, and you deleted one of them from another view
- // (like the view in the stand alone msg window)
- // since one item is selected, we should load it.
- var startIndex = {};
- var endIndex = {};
- treeSelection.getRangeAt(0, startIndex, endIndex);
-
- // select the selected item, so we'll load it
- treeSelection.select(startIndex.value);
- treeView.selectionChanged();
-
- EnsureRowInThreadTreeIsVisible(startIndex.value);
-
- UpdateMailToolbar("delete from another view, 1 row now selected");
- }
- else {
- // this can happen if you have more than 2 messages selected
- // in the thread pane, and you deleted one of them from another view
- // (like the view in the stand alone msg window)
- // since multiple messages are still selected, do nothing.
- }
- }
- else {
- if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None)
- {
- var viewSize = treeView.rowCount;
- if (gNextMessageViewIndexAfterDelete >= viewSize)
- {
- if (viewSize > 0)
- gNextMessageViewIndexAfterDelete = viewSize - 1;
- else
- {
- gNextMessageViewIndexAfterDelete = nsMsgViewIndex_None;
-
- // there is nothing to select since viewSize is 0
- treeSelection.clearSelection();
- setTitleFromFolder(folder, null);
- ClearMessagePane();
- UpdateMailToolbar("delete from current view, 0 rows left");
- }
- }
- }
-
- // if we are about to set the selection with a new element then DON'T clear
- // the selection then add the next message to select. This just generates
- // an extra round of command updating notifications that we are trying to
- // optimize away.
- if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None)
- {
- // When deleting a message we don't update the commands
- // when the selection goes to 0
- // (we have a hack in nsMsgDBView which prevents that update)
- // so there is no need to
- // update commands when we select the next message after the delete;
- // the commands already
- // have the right update state...
- gDBView.suppressCommandUpdating = true;
-
- // This check makes sure that the tree does not perform a
- // selection on a non selected row (row < 0), else assertions will
- // be thrown.
- if (gNextMessageViewIndexAfterDelete >= 0)
- treeSelection.select(gNextMessageViewIndexAfterDelete);
-
- // If gNextMessageViewIndexAfterDelete has the same value
- // as the last index we had selected, the tree won't generate a
- // selectionChanged notification for the tree view. So force a manual
- // selection changed call.
- // (don't worry it's cheap if we end up calling it twice).
- if (treeView)
- treeView.selectionChanged();
-
- EnsureRowInThreadTreeIsVisible(gNextMessageViewIndexAfterDelete);
- gDBView.suppressCommandUpdating = false;
-
- // hook for extra toolbar items
- // XXX TODO
- // I think there is a bug in the suppression code above.
- // What if I have two rows selected, and I hit delete,
- // and so we load the next row.
- // What if I have commands that only enable where
- // exactly one row is selected?
- UpdateMailToolbar("delete from current view, at least one row selected");
- }
- }
-
- // default value after delete/move/copy is over
- gNextMessageViewIndexAfterDelete = -2;
-}
-
-function HandleCompactCompleted(folder)
-{
- if (folder && folder.server.type != "imap")
- {
- var msgFolder = msgWindow.openFolder;
- if (msgFolder && folder.URI == msgFolder.URI)
- {
- // pretend the selection changed, to reselect the current folder+view.
- gMsgFolderSelected = null;
- msgWindow.openFolder = null;
- FolderPaneSelectionChange();
- LoadCurrentlyDisplayedMessage();
- }
- }
-}
-
-function LoadCurrentlyDisplayedMessage()
-{
- var scrolled = (gCurrentlyDisplayedMessage != nsMsgViewIndex_None);
- if (scrolled)
- {
- var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
- var treeSelection = treeView.selection;
- treeSelection.select(gCurrentlyDisplayedMessage);
- if (treeView)
- treeView.selectionChanged();
- EnsureRowInThreadTreeIsVisible(gCurrentlyDisplayedMessage);
- SetFocusThreadPane();
- gCurrentlyDisplayedMessage = nsMsgViewIndex_None; //reset
- }
- return scrolled;
-}
-
-function IsCurrentLoadedFolder(folder)
-{
- var msgfolder = folder.QueryInterface(Components.interfaces.nsIMsgFolder);
- var currentLoadedFolder = GetThreadPaneFolder();
- if (currentLoadedFolder.flags & Components.interfaces.nsMsgFolderFlags.Virtual)
- {
- var dbFolderInfo = currentLoadedFolder.msgDatabase.dBFolderInfo;
- var srchFolderUri = dbFolderInfo.getCharProperty("searchFolderUri");
- var re = new RegExp("^" + msgfolder.URI + "$|^" + msgfolder.URI + "\||\|" +
- msgfolder.URI + "$|\|" + msgfolder.URI +"\|");
-
- return currentLoadedFolder.URI.match(re);
- }
-
- return (currentLoadedFolder.URI == folder.URI);
-}
-
function ServerContainsFolder(server, folder)
{
if (!folder || !server)
return false;
return server.equals(folder.server);
}
@@ -663,29 +274,37 @@ function OnLoadMessenger()
}
gPrefBranch.QueryInterface(Components.interfaces.nsIPrefBranch2);
gPrefBranch.addObserver("mail.pane_config.dynamic", MailPrefObserver, false);
MailOfflineMgr.init();
CreateMailWindowGlobals();
GetMessagePane().collapsed = true;
+
+ // - initialize tabmail system
+ // Do this before LoadPostAccountWizard since that code selects the first
+ // folder for display, and we want gFolderDisplay setup and ready to handle
+ // that event chain.
+ // Also, we definitely need to register the tab type prior to the call to
+ // specialTabs.openSpecialTabsOnStartup below.
+ let tabmail = document.getElementById('tabmail');
+ if (tabmail)
+ {
+ tabmail.registerTabType(mailTabType);
+ tabmail.registerTabMonitor(QuickSearchTabMonitor);
+ tabmail.openFirstTab();
+ }
+
// verifyAccounts returns true if the callback won't be called
// We also don't want the account wizard to open if any sort of account exists
if (verifyAccounts(LoadPostAccountWizard, false))
LoadPostAccountWizard();
- // initialize tabmail system
- let tabmail = document.getElementById('tabmail');
- if (tabmail)
- {
- tabmail.registerTabType(mailTabType);
- tabmail.openFirstTab();
- }
-
+ // This also registers the contentTabType ("contentTab")
specialTabs.openSpecialTabsOnStartup();
window.addEventListener("AppCommand", HandleAppCommandEvent, true);
}
function LoadPostAccountWizard()
{
InitMsgWindow();
@@ -702,89 +321,88 @@ function LoadPostAccountWizard()
gPhishingDetector.init();
AddToSession();
//need to add to session before trying to load start folder otherwise listeners aren't
//set up correctly.
// argument[0] --> folder uri
- // argument[1] --> optional message key
- // argument[2] --> optional email address; // Will come from aim; needs to show msgs from buddy's email address.
if ("arguments" in window)
{
// filter our any feed urls that came in as arguments to the new window...
if (window.arguments.length && /^feed:/i.test(window.arguments[0] ))
{
var feedHandler = Components.classes["@mozilla.org/newsblog-feed-downloader;1"].getService(Components.interfaces.nsINewsBlogFeedDownloader);
if (feedHandler)
feedHandler.subscribeToFeed(window.arguments[0], null, msgWindow);
gStartFolderUri = null;
}
else
gStartFolderUri = (window.arguments.length > 0) ? window.arguments[0] : null;
- gStartMsgKey = (window.arguments.length > 1) ? window.arguments[1]: nsMsgKey_None;
- gSearchEmailAddress = (window.arguments.length > 2) ? window.arguments[2] : null;
}
function completeStartup() {
-#ifdef HAVE_SHELL_SERVICE
// Check whether we need to show the default client dialog
// First, check the shell service
var nsIShellService = Components.interfaces.nsIShellService;
- var shellService;
- var defaultAccount;
- try {
- shellService = Components.classes["@mozilla.org/mail/shell-service;1"].getService(nsIShellService);
- defaultAccount = accountManager.defaultAccount;
- } catch (ex) {}
+ if (nsIShellService) {
+ var shellService;
+ var defaultAccount;
+ try {
+ shellService = Components.classes["@mozilla.org/mail/shell-service;1"].getService(nsIShellService);
+ defaultAccount = accountManager.defaultAccount;
+ } catch (ex) {}
- // Next, try loading the search integration module
- // We'll get a null SearchIntegration if we don't have one
- Components.utils.import("resource://app/modules/SearchIntegration.js");
+ // Next, try loading the search integration module
+ // We'll get a null SearchIntegration if we don't have one
+ Components.utils.import("resource://app/modules/SearchIntegration.js");
- // Show the default client dialog only if
- // EITHER: we have at least one account, and we aren't already the default
- // for mail,
- // OR: we have the search integration module, the OS version is suitable,
- // and the first run hasn't already been completed.
- // Needs to be shown outside the he normal load sequence so it doesn't appear
- // before any other displays, in the wrong place of the screen.
- if ((shellService && defaultAccount && shellService.shouldCheckDefaultClient
- && !shellService.isDefaultClient(true, nsIShellService.MAIL)) ||
+ // Show the default client dialog only if
+ // EITHER: we have at least one account, and we aren't already the default
+ // for mail,
+ // OR: we have the search integration module, the OS version is suitable,
+ // and the first run hasn't already been completed.
+ // Needs to be shown outside the he normal load sequence so it doesn't appear
+ // before any other displays, in the wrong place of the screen.
+ if ((shellService && defaultAccount && shellService.shouldCheckDefaultClient
+ && !shellService.isDefaultClient(true, nsIShellService.MAIL)) ||
(SearchIntegration && !SearchIntegration.osVersionTooLow &&
!SearchIntegration.osComponentsNotRunning && !SearchIntegration.firstRunDone))
- window.openDialog("chrome://messenger/content/systemIntegrationDialog.xul",
- "SystemIntegration", "modal,centerscreen,chrome,resizable=no");
-#endif
+ window.openDialog("chrome://messenger/content/systemIntegrationDialog.xul",
+ "SystemIntegration", "modal,centerscreen,chrome,resizable=no");
+ }
// All core modal dialogs are done, the user can now interact with the 3-pane window
var obs = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
obs.notifyObservers(window, "mail-startup-done", null);
}
setTimeout(completeStartup, 0);
// FIX ME - later we will be able to use onload from the overlay
OnLoadMsgHeaderPane();
- gHaveLoadedMessage = false;
-
//Set focus to the Thread Pane the first time the window is opened.
SetFocusThreadPane();
// initialize the customizeDone method on the customizeable toolbar
var toolbox = document.getElementById("mail-toolbox");
toolbox.customizeDone = function(aEvent) { MailToolboxCustomizeDone(aEvent, "CustomizeMailToolbar"); };
var toolbarset = document.getElementById('customToolbars');
toolbox.toolbarset = toolbarset;
- loadStartFolder(gStartFolderUri);
+ // XXX Do not select the folder until the window displays or the threadpane
+ // will be at minimum size. We used to have
+ // gFolderDisplay.ensureRowIsVisible use settimeout itself to defer that
+ // calculation, but that was ugly. Also, in theory we will open the window
+ // faster if we let the event loop start doing things sooner.
+ window.setTimeout(loadStartFolder, 0, gStartFolderUri);
}
function HandleAppCommandEvent(evt)
{
evt.stopPropagation();
switch (evt.command) {
case "Back":
goDoCommand('cmd_goBack');
@@ -803,24 +421,31 @@ function HandleAppCommandEvent(evt)
break;
case "Home":
case "Reload":
default:
break;
}
}
+/**
+ * Called by messenger.xul:onunload, the 3-pane window inside of tabs window.
+ * It's being unloaded! Right now!
+ */
function OnUnloadMessenger()
{
- OnLeavingFolder(gMsgFolderSelected); // mark all read in current folder
accountManager.removeIncomingServerListener(gThreePaneIncomingServerListener);
gPrefBranch.QueryInterface(Components.interfaces.nsIPrefBranch2);
gPrefBranch.removeObserver("mail.pane_config.dynamic", MailPrefObserver);
document.getElementById('tabmail').closeTabs();
+ var mailSession = Components.classes["@mozilla.org/messenger/services/session;1"]
+ .getService(Components.interfaces.nsIMsgMailSession);
+ mailSession.RemoveFolderListener(folderListener);
+
gPhishingDetector.shutdown();
// FIX ME - later we will be able to use onload from the overlay
OnUnloadMsgHeaderPane();
UnloadPanes();
OnMailWindowUnload();
@@ -968,17 +593,17 @@ function InitPanes()
{
gFolderTreeView.load(document.getElementById("folderTree"),
"folderTree.json");
var folderTree = document.getElementById("folderTree");
folderTree.addEventListener("click",FolderPaneOnClick,true);
folderTree.addEventListener("mousedown",TreeOnMouseDown,true);
var threadTree = document.getElementById("threadTree");
threadTree.addEventListener("click",ThreadTreeOnClick,true);
-
+
OnLoadThreadPane();
SetupCommandUpdateHandlers();
}
function UnloadPanes()
{
var threadTree = document.getElementById("threadTree");
threadTree.removeEventListener("click",ThreadTreeOnClick,true);
@@ -1004,73 +629,73 @@ function UpgradeThreadPaneUI()
threadPaneUIVersion = gPrefBranch.getIntPref("mailnews.ui.threadpane.version");
if (threadPaneUIVersion < 7)
{
// Mark all imap folders as offline at the very first run of TB v3
// We use the threadpane ui version to determine TB profile version
let servers = Components.classes["@mozilla.org/messenger/account-manager;1"]
.getService(Components.interfaces.nsIMsgAccountManager).allServers;
-
+
for each (let server in fixIterator(servers, Components.interfaces.nsIMsgIncomingServer))
{
if (server.type != "imap")
continue;
-
+
let allFolders = Components.classes["@mozilla.org/supports-array;1"]
.createInstance(Components.interfaces.nsISupportsArray);
server.rootFolder.ListDescendents(allFolders);
for each (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder))
folder.setFlag(Components.interfaces.nsMsgFolderFlags.Offline);
}
-
+
// Note: threadTree._reorderColumn will throw an ERROR if the columns specified are already in the same order!
if (threadPaneUIVersion < 6)
{
var threadTree = document.getElementById("threadTree");
var dateCol = document.getElementById("dateCol");
var receivedCol = document.getElementById("receivedCol");
var junkCol = document.getElementById("junkStatusCol");
-
+
if (threadPaneUIVersion < 5)
{
if (threadPaneUIVersion < 4)
{
if (threadPaneUIVersion < 3)
{
-
+
// in thunderbird, we are inserting the junk column just before the
// date column.
threadTree._reorderColumn(junkCol, dateCol, true);
}
-
+
var senderCol = document.getElementById("senderCol");
var recipientCol = document.getElementById("recipientCol");
threadTree._reorderColumn(recipientCol, junkCol, true);
threadTree._reorderColumn(senderCol, recipientCol, true);
-
+
} // version 4 upgrades
-
+
// version 5 adds a new column called attachments
var attachmentCol = document.getElementById("attachmentCol");
var subjectCol = document.getElementById("subjectCol");
-
+
threadTree._reorderColumn(attachmentCol, subjectCol, true);
-
+
} // version 5 upgrades
-
+
if (dateCol)
threadTree._reorderColumn(receivedCol, dateCol, true);
else
threadTree._reorderColumn(receivedCol, junkCol, false);
-
+
} // version 6 upgrades
-
+
gPrefBranch.setIntPref("mailnews.ui.threadpane.version", 7);
-
+
} // version 7 upgrades
}
catch (ex) {
dump("UpgradeThreadPane: ex = " + ex + "\n");
}
}
function OnLoadThreadPane()
@@ -1145,95 +770,114 @@ function GetTotalCountElement()
function IsMessagePaneCollapsed()
{
return GetMessagePane().collapsed;
}
function ClearThreadPaneSelection()
{
- try {
- if (gDBView) {
- var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
- var treeSelection = treeView.selection;
- if (treeSelection)
- treeSelection.clearSelection();
- }
- }
- catch (ex) {
- dump("ClearThreadPaneSelection: ex = " + ex + "\n");
- }
+ gFolderDisplay.clearSelection();
}
function ClearMessagePane()
{
- if(gHaveLoadedMessage)
- {
- gHaveLoadedMessage = false;
- if (GetMessagePaneFrame().location.href != "about:blank")
- GetMessagePaneFrame().location.href = "about:blank";
-
- // hide the message header view AND the message pane...
- HideMessageHeaderPane();
- gMessageNotificationBar.clearMsgNotifications();
- ClearPendingReadTimer();
- }
+ // hide the message header view AND the message pane...
+ HideMessageHeaderPane();
+ gMessageNotificationBar.clearMsgNotifications();
+ ClearPendingReadTimer();
+ GetMessagePaneFrame().location.href = "about:blank";
}
-// Function to change the highlighted row to where the mouse was clicked
-// without loading the contents of the selected row.
-// It will also keep the outline/dotted line in the original row.
-function ChangeSelectionWithoutContentLoad(event, tree)
+/**
+ * When right-clicks happen, we do not want to corrupt the underlying
+ * selection. The right-click is a transient selection. So, unless the
+ * user is right-clicking on the current selection, we create a new
+ * selection object (thanks to JSTreeSelection) and set that as the
+ * current/transient selection.
+ *
+ * It is up you to call RestoreSelectionWithoutContentLoad to clean up when we
+ * are done.
+ *
+ * @param aSingleSelect Should the selection we create be a single selection?
+ * This is relevant if the row being clicked on is already part of the
+ * selection. If it is part of the selection and !aSingleSelect, then we
+ * leave the selection as is. If it is part of the selection and
+ * aSingleSelect then we create a transient single-row selection.
+ */
+function ChangeSelectionWithoutContentLoad(event, tree, aSingleSelect)
{
- var treeBoxObj = tree.treeBoxObject;
- if (!treeBoxObj)
- {
- event.stopPropagation();
- return;
- }
- var treeSelection = treeBoxObj.view.selection;
+ var treeBoxObj = tree.treeBoxObject;
+ if (!treeBoxObj) {
+ event.stopPropagation();
+ return;
+ }
+
+ var treeSelection = treeBoxObj.view.selection;
- var row = treeBoxObj.getRowAt(event.clientX, event.clientY);
- // make sure that row.value is valid so that it doesn't mess up
- // the call to ensureRowIsVisible().
- if((row >= 0) && !treeSelection.isSelected(row))
- {
- var saveCurrentIndex = treeSelection.currentIndex;
- treeSelection.selectEventsSuppressed = true;
- treeSelection.select(row);
- treeSelection.currentIndex = saveCurrentIndex;
- treeBoxObj.ensureRowIsVisible(row);
- treeSelection.selectEventsSuppressed = false;
+ var row = treeBoxObj.getRowAt(event.clientX, event.clientY);
+ // Only do something if:
+ // - the row is valid
+ // - it's not already selected (or we want a single selection)
+ if (row >= 0 &&
+ (aSingleSelect || !treeSelection.isSelected(row))) {
+ // Check if the row is exactly the existing selection. In that case
+ // there is no need to create a bogus selection.
+ if (treeSelection.count == 1) {
+ let minObj = {};
+ treeSelection.getRangeAt(0, minObj, {});
+ if (minObj.value == row) {
+ event.stopPropagation();
+ return;
+ }
+ }
+
+ let transientSelection = new JSTreeSelection(treeBoxObj);
+ transientSelection.logAdjustSelectionForReplay();
- // Keep track of which row in the thread pane is currently selected.
- if(tree.id == "threadTree")
- gThreadPaneCurrentSelectedIndex = row;
- }
- event.stopPropagation();
+ gRightMouseButtonSavedSelection = {
+ view: treeBoxObj.view,
+ realSelection: treeSelection,
+ transientSelection: transientSelection
+ };
+
+ var saveCurrentIndex = treeSelection.currentIndex;
+
+ // tell it to log calls to adjustSelection
+ // attach it to the view
+ treeBoxObj.view.selection = transientSelection;
+ // Don't generate any selection events! (we never set this to false, because
+ // that would generate an event, and we never need one of those from this
+ // selection object.
+ transientSelection.selectEventsSuppressed = true;
+ transientSelection.select(row);
+ transientSelection.currentIndex = saveCurrentIndex;
+ treeBoxObj.ensureRowIsVisible(row);
+ }
+ event.stopPropagation();
}
function TreeOnMouseDown(event)
{
// Detect right mouse click and change the highlight to the row
// where the click happened without loading the message headers in
// the Folder or Thread Pane.
// Same for middle click, which will open the folder/message in a tab.
if (event.button == 2 || event.button == 1)
{
- gRightMouseButtonDown = true;
- ChangeSelectionWithoutContentLoad(event, event.target.parentNode);
+ // We want a single selection if this is a middle-click (button 1)
+ ChangeSelectionWithoutContentLoad(event, event.target.parentNode,
+ event.button == 1);
}
- else
- gRightMouseButtonDown = false;
}
function FolderPaneOnClick(event)
{
var folderTree = document.getElementById("folderTree");
-
+
// Middle click on a folder opens the folder in a tab
if (event.button == 1)
{
MsgOpenNewTabForFolder();
RestoreSelectionWithoutContentLoad(folderTree);
}
else if (event.button == 0)
{
@@ -1253,160 +897,43 @@ function FolderPaneOnClick(event)
event.stopPropagation();
}
}
}
function ThreadTreeOnClick(event)
{
var threadTree = document.getElementById("threadTree");
-
+
// Middle click on a message opens the message in a tab
if (event.button == 1)
{
MsgOpenNewTabForMessage();
RestoreSelectionWithoutContentLoad(threadTree);
}
}
function GetSelectedMsgFolders()
{
return gFolderTreeView.getSelectedFolders();
}
-function GetFirstSelectedMessage()
-{
- try {
- return gDBView.URIForFirstSelectedMessage;
- }
- catch (ex) {
- return null;
- }
-}
-
-function GetSelectedIndices(dbView)
-{
- try {
- return dbView.getIndicesForSelection({});
- }
- catch (ex) {
- dump("ex = " + ex + "\n");
- return null;
- }
-}
-
-function GetSelectedMessages()
-{
- if (!GetDBView())
- return null;
-
- var messageArray = GetDBView().getURIsForSelection({});
- return messageArray.length ? messageArray : null;
-}
-
function GetLoadedMsgFolder()
{
- if (!gDBView) return null;
- return gDBView.msgFolder;
-}
-
-function GetLoadedMessage()
-{
- try {
- return gDBView.URIForFirstSelectedMessage;
- }
- catch (ex) {
- return null;
- }
-}
-
-//Clear everything related to the current message. called after load start page.
-function ClearMessageSelection()
-{
- ClearThreadPaneSelection();
-}
-
-// Figures out how many messages are selected (hilighted - does not necessarily
-// have the dotted outline) above a given index row value in the thread pane.
-function NumberOfSelectedMessagesAboveCurrentIndex(index)
-{
- var numberOfMessages = 0;
- var indicies = GetSelectedIndices(gDBView);
-
- if (indicies && indicies.length)
- {
- for (var i = 0; i < indicies.length; i++)
- {
- if (indicies[i] < index)
- ++numberOfMessages;
- else
- break;
- }
- }
- return numberOfMessages;
-}
-
-function SetNextMessageAfterDelete()
-{
- var treeSelection = GetThreadTree().view.selection;
-
- if (treeSelection.isSelected(treeSelection.currentIndex))
- {
- gNextMessageViewIndexAfterDelete = gDBView.msgToSelectAfterDelete;
- gSelectedIndexWhenDeleting = treeSelection.currentIndex;
- }
- else if(gDBView.removeRowOnMoveOrDelete)
- {
- // Only set gThreadPaneDeleteOrMoveOccurred to true if the message was
- // truly moved to the trash or deleted, as opposed to an IMAP delete
- // (where it is only "marked as deleted". This will prevent bug 142065.
- //
- // If it's an IMAP delete, then just set gNextMessageViewIndexAfterDelete
- // to treeSelection.currentIndex (where the outline is at) because nothing
- // was moved or deleted from the folder.
- gThreadPaneDeleteOrMoveOccurred = true;
- gNextMessageViewIndexAfterDelete = treeSelection.currentIndex - NumberOfSelectedMessagesAboveCurrentIndex(treeSelection.currentIndex);
- }
- else
- gNextMessageViewIndexAfterDelete = treeSelection.currentIndex;
+ return gFolderDisplay.displayedFolder;
}
function SelectFolder(folderUri)
{
gFolderTreeView.selectFolder(GetMsgFolderFromUri(folderUri));
}
-function SelectMessage(messageUri)
-{
- var msgHdr = messenger.messageServiceFromURI(messageUri).messageURIToMsgHdr(messageUri);
- if (msgHdr)
- gDBView.selectMsgByKey(msgHdr.messageKey);
-}
-
function ReloadMessage()
{
- gDBView.reloadMessage();
-}
-
-function GetDBView()
-{
- return gDBView;
-}
-
-function LoadNavigatedToMessage(msgHdr, folder, folderUri)
-{
- if (IsCurrentLoadedFolder(folder))
- {
- gDBView.selectMsgByKey(msgHdr.messageKey);
- }
- else
- {
- gStartMsgKey = msgHdr.messageKey;
- SelectFolder(folderUri);
- }
+ gFolderDisplay.view.dbView.reloadMessage();
}
// Some of the per account junk mail settings have been
// converted to global prefs. Let's try to migrate some
// of those settings from the default account.
function MigrateJunkMailSettings()
{
var junkMailSettingsVersion = gPrefBranch.getIntPref("mail.spam.version");
@@ -1479,18 +1006,18 @@ function MigrateAttachmentDownloadStore(
gPrefBranch.setIntPref("mail.attachment.store.version", 1);
}
}
function threadPaneOnDragStart(aEvent) {
if (aEvent.originalTarget.localName != "treechildren")
return;
- var messages = GetSelectedMessages();
+ var messages = gFolderDisplay.selectedMessageUris;
if (!messages)
return;
- SetNextMessageAfterDelete()
+ gFolderDisplay.hintAboutToDeleteMessages();
for (let i in messages)
aEvent.dataTransfer.mozSetDataAt("text/x-moz-message", messages[i], i);
aEvent.dataTransfer.effectAllowed = "copyMove";
aEvent.dataTransfer.addElement(aEvent.originalTarget);
}
rename from mailnews/base/resources/content/msgViewNavigation.js
rename to mail/base/content/msgViewNavigation.js
--- a/mailnews/base/resources/content/msgViewNavigation.js
+++ b/mail/base/content/msgViewNavigation.js
@@ -224,103 +224,50 @@ function CrossFolderNavigation(type)
var folder = FindNextFolder();
if (folder && (gDBView.msgFolder.URI != folder.URI))
{
switch (nextMode)
{
case 0:
// do this unconditionally
- gNextMessageAfterLoad = type;
SelectFolder(folder.URI);
break;
case 1:
default:
var promptText = gMessengerBundle.getFormattedString("advanceNextPrompt", [ folder.name ], 1);
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
if (!promptService.confirmEx(window, null, promptText,
promptService.STD_YES_NO_BUTTONS,
null, null, null, null, {}))
{
- gNextMessageAfterLoad = type;
+ gFolderDisplay.pushNavigation(type, true);
SelectFolder(folder.URI);
}
break;
}
}
}
else
{
// if no message is loaded, relPos should be 0, to
// go back to the previously loaded message
var relPos = (type == nsMsgNavigationType.forward)
- ? 1 : ((GetLoadedMessage()) ? -1 : 0);
+ ? 1 : ((gMessageDisplay.displayedMessage) ? -1 : 0);
var folderUri = messenger.getFolderUriAtNavigatePos(relPos);
var msgHdr = messenger.msgHdrFromURI(messenger.getMsgUriAtNavigatePos(relPos));
gStartMsgKey = msgHdr.messageKey;
var curPos = messenger.navigatePos;
curPos += relPos;
messenger.navigatePos = curPos;
SelectFolder(folderUri);
}
}
-
-function ScrollToMessage(type, wrap, selectMessage)
-{
- try {
- var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
- var treeSelection = treeView.selection;
- var currentIndex = treeSelection.currentIndex;
-
- var resultId = new Object;
- var resultIndex = new Object;
- var threadIndex = new Object;
-
- let elidedFlag = Components.interfaces.nsMsgMessageFlags.Elided;
- let summarizeSelection =
- gPrefBranch.getBoolPref("mail.operate_on_msgs_in_collapsed_threads");
-
- // if we're doing next unread, and a collapsed thread is selected, and
- // the top level message is unread, just set the result manually to
- // the top level message, without using gDBView.viewNavigate.
- if (summarizeSelection && type == nsMsgNavigationType.nextUnreadMessage &&
- currentIndex != -1 &&
- gDBView.getFlagsAt(currentIndex) & elidedFlag &&
- gDBView.isContainer(currentIndex) &&
- ! (gDBView.getFlagsAt(currentIndex) &
- Components.interfaces.nsMsgMessageFlags.Read)) {
- resultIndex.value = currentIndex;
- resultId.value = gDBView.getKeyAt(currentIndex);
- } else {
- gDBView.viewNavigate(type, resultId, resultIndex, threadIndex, true /* wrap */);
- }
-
- // only scroll and select if we found something
- if ((resultId.value != nsMsgViewIndex_None) && (resultIndex.value != nsMsgViewIndex_None)) {
- if (gDBView.getFlagsAt(resultIndex.value) & elidedFlag &&
- summarizeSelection)
- gDBView.toggleOpenState(resultIndex.value);
-
- if (selectMessage){
- treeSelection.select(resultIndex.value);
- }
- EnsureRowInThreadTreeIsVisible(resultIndex.value);
- return true;
- }
- else {
- return false;
- }
- }
- catch (ex) {
- return false;
- }
-}
-
function GoNextMessage(type, startFromBeginning)
{
- if (!ScrollToMessage(type, startFromBeginning, true))
+ if (!gFolderDisplay.navigate(type))
CrossFolderNavigation(type);
SetFocusThreadPaneIfNotOnMessagePane();
}
--- a/mail/base/content/phishingDetector.js
+++ b/mail/base/content/phishingDetector.js
@@ -1,45 +1,44 @@
-# -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is Thunderbird Phishing Dectector
-#
-# The Initial Developer of the Original Code is
-# The Mozilla Foundation.
-# Portions created by the Initial Developer are Copyright (C) 2005
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# Scott MacGregor <mscott@mozilla.org>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK ******
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Phishing Dectector
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Scott MacGregor <mscott@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ****** */
// Dependencies:
// gPrefBranch, gBrandBundle, gMessengerBundle should already be defined
// gatherTextUnder from utilityOverlay.js
const kPhishingNotSuspicious = 0;
const kPhishingWithIPAddress = 1;
const kPhishingWithMismatchedHosts = 2;
@@ -182,36 +181,39 @@ var gPhishingDetector = {
failsStaticTests = (aLinkText &&
this.misMatchedHostWithLinkText(hrefURL, aLinkText, linkTextURL))
}
}
// Lookup the url against our local list. We want to do this even if the url fails our static
// test checks because the url might be in the white list.
if (this.mPhishingWarden)
- this.mPhishingWarden.isEvilURL(GetLoadedMessage(), failsStaticTests, aUrl, this.localListCallback);
+ this.mPhishingWarden.isEvilURL(gFolderDisplay.selectedMessage,
+ failsStaticTests, aUrl,
+ this.localListCallback);
else
- this.localListCallback(GetLoadedMessage(), failsStaticTests, aUrl, 2 /* not found */);
+ this.localListCallback(gFolderDisplay.selectedMessage,
+ failsStaticTests, aUrl, 2 /* not found */);
}
},
/**
*
- * @param aMsgURI the uri for the loaded message when the look up was initiated.
+ * @param aMsgHdr the header for the loaded message when the look up was initiated.
* @param aFailsStaticTests true if our static tests think the url is a phishing scam
* @param aUrl the url we looked up in the phishing tables
* @param aLocalListStatus the result of the local lookup (PROT_ListWarden.IN_BLACKLIST,
* PROT_ListWarden.IN_WHITELIST or PROT_ListWarden.NOT_FOUND.
*/
- localListCallback: function (aMsgURI, aFailsStaticTests, aUrl, aLocalListStatus)
+ localListCallback: function (aMsgHdr, aFailsStaticTests, aUrl, aLocalListStatus)
{
// for urls in the blacklist, notify the phishing bar.
// for urls in the whitelist, do nothing
// for all other urls, fall back to the static tests
- if (aMsgURI == GetLoadedMessage())
+ if (aMsgHdr == gFolderDisplay.selectedMessage)
{
if (aLocalListStatus == 0 /* PROT_ListWarden.IN_BLACKLIST */ ||
(aLocalListStatus == 2 /* PROT_ListWarden.PROT_ListWarden.NOT_FOUND */ && aFailsStaticTests))
gMessageNotificationBar.setPhishingMsg();
}
},
/**
--- a/mail/base/content/search.xml
+++ b/mail/base/content/search.xml
@@ -69,31 +69,34 @@
<constructor>
<![CDATA[
// initialize the quick search mode based on the checked menu item
var desiredQuickSearchMode = document.getElementById('quick-search-menupopup').getAttribute('value');
var menuItems = document.getElementById('quick-search-menupopup').getElementsByAttribute('value', '*');
var selectedMenuItem;
for (var index = 0; index < menuItems.length; index++)
- if (menuItems[index].value == desiredQuickSearchMode)
+ if (menuItems[index].getAttribute("value") == desiredQuickSearchMode)
{
selectedMenuItem = menuItems[index];
break;
}
// if we failed to find selectedMenuItemVal in our array of menuitems
// then just use the first menu item in the array (surely we have at least one menu item!)
// This scenario happens when we decide to obsolete/delete search modes from
// the quick search drop down.
- if (!selectedMenuItem)
+ if (!selectedMenuItem && menuItems.length) {
selectedMenuItem = menuItems[0];
-
- selectedMenuItem.setAttribute('checked', 'true');
- this.mQuickSearchMode = selectedMenuItem.value; // the checked menu item
+
+ selectedMenuItem.setAttribute('checked', 'true');
+ this.mQuickSearchMode =
+ selectedMenuItem.getAttribute("value");
+ }
+
this.setSearchCriteriaText();
]]>
</constructor>
<property name="showingSearchCriteria" onget="return this.getAttribute('searchCriteria') == 'true';"
onset="this.setAttribute('searchCriteria', val); return val;"/>
<property name="clearButtonHidden" onget="return document.getElementById('quick-search-clearbutton').getAttribute('clearButtonHidden') == 'true';"
@@ -127,17 +130,17 @@
<property name="searchMode" onget="return this.mQuickSearchMode;"
onset="this.mQuickSearchMode = val; document.getElementById('quick-search-menupopup').setAttribute('value', val);"/>
<method name="setSearchCriteriaText">
<body><![CDATA[
this.showingSearchCriteria = true;
// extract the label value from the menu item
- var menuItems = document.getElementById('quick-search-menupopup').getElementsByAttribute('value', this.searchMode);
+ var menuItems = document.getElementById('quick-search-menupopup').getElementsByAttribute('value', this.searchMode);
this.inputField.value = menuItems[0].getAttribute('label');
this.clearButtonHidden = true;
]]></body>
</method>
<method name="openmenupopup">
<body>
<![CDATA[
--- a/mail/base/content/searchBar.js
+++ b/mail/base/content/searchBar.js
@@ -1,551 +1,159 @@
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is Mozilla Communicator client code, released
-# March 31, 1998.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 1998-1999
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# Seth Spitzer <sspitzer@netscape.com>
-# Scott MacGregor <mscott@mozilla.org>
-# David Bienvenu <bienvenu@nventure.com>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either of the GNU General Public License Version 2 or later (the "GPL"),
-# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Seth Spitzer <sspitzer@netscape.com>
+ * Scott MacGregor <mscott@mozilla.org>
+ * David Bienvenu <bienvenu@nventure.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
-var gSearchSession = null;
-var gPreQuickSearchView = null;
-var gSearchTimer = null;
-var gViewSearchListener;
+Components.utils.import("resource://app/modules/quickSearchManager.js");
+
var gSearchBundle;
var gStatusBar = null;
-var gSearchInProgress = false;
-var gDefaultSearchViewTerms = null;
-var gQSViewIsDirty = false;
var gIgnoreFocus = false;
var gIgnoreClick = false;
-var gNumTotalMessages;
-var gNumUnreadMessages;
+
+// change/add constants in QuickSearchConstants in quickSearchManager.js first
+// ideally these should go away in favor of everyone using QuickSearchConstants
+const kQuickSearchSubject = QuickSearchConstants.kQuickSearchSubject;
+const kQuickSearchFrom = QuickSearchConstants.kQuickSearchFrom;
+const kQuickSearchFromOrSubject =
+ QuickSearchConstants.kQuickSearchFromOrSubject;
+const kQuickSearchBody = QuickSearchConstants.kQuickSearchBody;
+const kQuickSearchRecipient = QuickSearchConstants.kQuickSearchRecipient;
+const kQuickSearchRecipientOrSubject =
+ QuickSearchConstants.kQuickSearchRecipientOrSubject;
+
-// search criteria mode values
-// Note: If you change these constants, please update the menuitem values in
-// quick-search-menupopup. Note: These values are stored in localstore.rdf so we
-// can remember the users last quick search state. If you add values here, you must add
-// them to the end of the list!
-const kQuickSearchSubject = 0;
-const kQuickSearchFrom = 1;
-const kQuickSearchFromOrSubject = 2;
-const kQuickSearchBody = 3;
-// const kQuickSearchHighlight = 4; // * We no longer support this quick search mode..*
-const kQuickSearchRecipient = 5;
-const kQuickSearchRecipientOrSubject = 6;
+/**
+ * We are exclusively concerned with disabling the quick-search box when a
+ * tab is being displayed that lacks quick search abilities.
+ */
+var QuickSearchTabMonitor = {
+ onTabTitleChanged: function() {
+ },
+
+ onTabSwitched: function (aTab, aOldTab) {
+ let searchInput = document.getElementById("searchInput");
+
+ if (searchInput) {
+ let newTabEligible = aTab.mode.tabType == mailTabType;
+ searchInput.disabled = !newTabEligible;
+ if (!newTabEligible)
+ searchInput.value = "";
+ }
+ },
+};
function SetQSStatusText(aNumHits)
{
var statusMsg;
// if there are no hits, it means no matches were found in the search.
if (aNumHits == 0)
statusMsg = gSearchBundle.getString("searchFailureMessage");
- else
+ else
{
- if (aNumHits == 1)
+ if (aNumHits == 1)
statusMsg = gSearchBundle.getString("searchSuccessMessage");
else
statusMsg = gSearchBundle.getFormattedString("searchSuccessMessages", [aNumHits]);
}
statusFeedback.showStatusString(statusMsg);
}
-// nsIMsgSearchNotify object
-var gSearchNotificationListener =
-{
- onSearchHit: function(header, folder)
- {
- gNumTotalMessages++;
- if (!header.isRead)
- gNumUnreadMessages++;
- // XXX todo
- // update status text?
- },
-
- onSearchDone: function(status)
- {
- SetQSStatusText(gDBView.QueryInterface(Components.interfaces.nsITreeView).rowCount)
- statusFeedback.showProgress(0);
- gStatusBar.setAttribute("mode","normal");
- gSearchInProgress = false;
-
- // ### TODO need to find out if there's quick search within a virtual folder.
- if (gCurrentVirtualFolderUri &&
- (!gSearchInput || gSearchInput.value == "" || gSearchInput.showingSearchCriteria))
- {
- var vFolder = GetMsgFolderFromUri(gCurrentVirtualFolderUri, false);
- var dbFolderInfo = vFolder.msgDatabase.dBFolderInfo;
- dbFolderInfo.numUnreadMessages = gNumUnreadMessages;
- dbFolderInfo.numMessages = gNumTotalMessages;
- vFolder.updateSummaryTotals(true); // force update from db.
- const MSG_DB_LARGE_COMMIT = 1;
- vFolder.msgDatabase.Commit(MSG_DB_LARGE_COMMIT);
- // now that we have finished loading a virtual folder,
- // scroll to the correct message if there is at least one.
- if (vFolder.getTotalMessages(false) > 0)
- ScrollToMessageAfterFolderLoad(vFolder);
- }
- },
-
- onNewSearch: function()
- {
- statusFeedback.showProgress(0);
- statusFeedback.showStatusString(gSearchBundle.getString("searchingMessage"));
- gStatusBar.setAttribute("mode","undetermined");
- gSearchInProgress = true;
- gNumTotalMessages = 0;
- gNumUnreadMessages = 0;
- }
-}
-
function getDocumentElements()
{
- gSearchBundle = document.getElementById("bundle_search");
+ gSearchBundle = document.getElementById("bundle_search");
gStatusBar = document.getElementById('statusbar-icon');
GetSearchInput();
}
-function addListeners()
-{
- gViewSearchListener = gDBView.QueryInterface(Components.interfaces.nsIMsgSearchNotify);
- gSearchSession.registerListener(gViewSearchListener);
-}
-
-function removeListeners()
-{
- gSearchSession.unregisterListener(gViewSearchListener);
-}
-
-function removeGlobalListeners()
-{
- removeListeners();
- gSearchSession.unregisterListener(gSearchNotificationListener);
-}
-
-function initializeGlobalListeners()
-{
- // Setup the javascript object as a listener on the search results
- gSearchSession.registerListener(gSearchNotificationListener);
-}
-
-function createQuickSearchView()
-{
- //if not already in quick search view
- if (gDBView.viewType != nsMsgViewType.eShowQuickSearchResults)
- {
- var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView); //clear selection
- if (treeView && treeView.selection)
- treeView.selection.clearSelection();
- gPreQuickSearchView = gDBView;
- if (gDBView.viewType == nsMsgViewType.eShowVirtualFolderResults)
- {
- // remove the view as a listener on the search results
- var saveViewSearchListener = gDBView.QueryInterface(Components.interfaces.nsIMsgSearchNotify);
- gSearchSession.unregisterListener(saveViewSearchListener);
- }
- var viewFlags = gDBView.viewFlags;
- CreateDBView(gDBView.msgFolder, (gXFVirtualFolderTerms) ? nsMsgViewType.eShowVirtualFolderResults : nsMsgViewType.eShowQuickSearchResults, viewFlags, gDBView.sortType, gDBView.sortOrder);
- }
-}
-
-function initializeSearchBar()
-{
- createQuickSearchView();
- if (!gSearchSession)
- {
- getDocumentElements();
- var searchSessionContractID = "@mozilla.org/messenger/searchSession;1";
- gSearchSession = Components.classes[searchSessionContractID].createInstance(Components.interfaces.nsIMsgSearchSession);
- initializeGlobalListeners();
- }
- else
- {
- if (gSearchInProgress)
- {
- onSearchStop();
- gSearchInProgress = false;
- }
- removeListeners();
- }
- addListeners();
-}
-
function onEnterInSearchBar()
{
- if (!gSearchInput || gSearchInput.value == "" || gSearchInput.showingSearchCriteria)
- {
- if (gDBView.viewType == nsMsgViewType.eShowQuickSearchResults
- || gDBView.viewType == nsMsgViewType.eShowVirtualFolderResults)
- {
- statusFeedback.showStatusString("");
-
- viewDebug ("onEnterInSearchBar gDefaultSearchViewTerms = " + gDefaultSearchViewTerms + "gVirtualFolderTerms = "
- + gVirtualFolderTerms + "gXFVirtualFolderTerms = " + gXFVirtualFolderTerms + "\n");
- var addTerms = gDefaultSearchViewTerms || gVirtualFolderTerms || gXFVirtualFolderTerms;
- if (addTerms)
- {
- viewDebug ("addTerms = " + addTerms + " count = " + addTerms.Count() + "\n");
- initializeSearchBar();
- onSearch(addTerms);
- }
- else
- restorePreSearchView();
- }
- else if (gPreQuickSearchView && !gDefaultSearchViewTerms)// may be a quick search from a cross-folder virtual folder
- restorePreSearchView();
-
- if (gSearchInput)
- gSearchInput.showingSearchCriteria = true;
-
- gQSViewIsDirty = false;
- return;
- }
-
- initializeSearchBar();
-
- ClearThreadPaneSelection();
- ClearMessagePane();
-
- onSearch(null);
- gQSViewIsDirty = false;
-}
-
-function restorePreSearchView()
-{
- var selectedHdr = null;
- //save selection
- try
- {
- selectedHdr = gDBView.hdrForFirstSelectedMessage;
- }
- catch (ex)
- {}
-
- //we might have to sort the view coming out of quick search
- var sortType = gDBView.sortType;
- var sortOrder = gDBView.sortOrder;
- var viewFlags = gDBView.viewFlags;
- var folder = gDBView.msgFolder;
-
- gDBView.close();
- gDBView = null;
+ if (!gSearchInput)
+ return;
- if (gPreQuickSearchView)
- {
- gDBView = gPreQuickSearchView;
- if (gDBView.viewType == nsMsgViewType.eShowVirtualFolderResults)
- {
- // read the view as a listener on the search results
- var saveViewSearchListener = gDBView.QueryInterface(Components.interfaces.nsIMsgSearchNotify);
- if (gSearchSession)
- gSearchSession.registerListener(saveViewSearchListener);
- }
-// dump ("view type = " + gDBView.viewType + "\n");
-
- if (sortType != gDBView.sortType || sortOrder != gDBView.sortOrder)
- {
- gDBView.sort(sortType, sortOrder);
- }
- UpdateSortIndicators(sortType, sortOrder);
-
- gPreQuickSearchView = null;
- }
- else //create default view type
- {
- CreateDBView(folder, nsMsgViewType.eShowAllThreads, viewFlags, sortType, sortOrder);
- }
+ // nothing changes while showing the criteria
+ if (gSearchInput.showingSearchCriteria)
+ return;
- RerootThreadPane();
-
- var scrolled = false;
-
- // now restore selection
- if (selectedHdr)
- {
- gDBView.selectMsgByKey(selectedHdr.messageKey);
- var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
- var selectedIndex = treeView.selection.currentIndex;
- if (selectedIndex >= 0)
- {
- // scroll
- EnsureRowInThreadTreeIsVisible(selectedIndex);
- scrolled = true;
- }
- else
- ClearMessagePane();
- }
-
- if (!scrolled)
- ScrollToMessageAfterFolderLoad(null);
-}
-
-function onSearch(aSearchTerms)
-{
- viewDebug("in OnSearch, searchTerms = " + aSearchTerms + "\n");
- RerootThreadPane();
-
- if (aSearchTerms)
- createSearchTermsWithList(aSearchTerms);
- else
- createSearchTerms();
-
- gDBView.searchSession = gSearchSession;
- try
- {
- gSearchSession.search(msgWindow);
- }
- catch(ex)
- {
- dump("Search Exception\n");
- }
+ if (!gSearchInput || gSearchInput.value == "")
+ gFolderDisplay.view.search.userTerms = null;
+ else
+ gFolderDisplay.view.search.quickSearch(gSearchInput.searchMode,
+ gSearchInput.value);
}
-function createSearchTermsWithList(aTermsArray)
-{
- var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
- var nsMsgSearchAttrib = Components.interfaces.nsMsgSearchAttrib;
- var nsMsgSearchOp = Components.interfaces.nsMsgSearchOp;
-
- gSearchSession.clearScopes();
- var searchTerms = gSearchSession.searchTerms;
- var searchTermsArray = searchTerms.QueryInterface(Components.interfaces.nsISupportsArray);
- searchTermsArray.Clear();
-
- var i;
- var selectedFolder = GetThreadPaneFolder();
- var ioService = Components.classes["@mozilla.org/network/io-service;1"]
- .getService(Components.interfaces.nsIIOService);
-
- var termsArray = aTermsArray.QueryInterface(Components.interfaces.nsISupportsArray);
-
- if (gXFVirtualFolderTerms)
- {
- var msgDatabase = selectedFolder.msgDatabase;
- if (msgDatabase)
- {
- var dbFolderInfo = msgDatabase.dBFolderInfo;
- var srchFolderUri = dbFolderInfo.getCharProperty("searchFolderUri");
- viewDebug("createSearchTermsWithList xf vf scope = " + srchFolderUri + "\n");
- var srchFolderUriArray = srchFolderUri.split('|');
- for (i in srchFolderUriArray)
- {
- var realFolder = GetMsgFolderFromUri(srchFolderUriArray[i]);
- if (!realFolder.isServer)
- gSearchSession.addScopeTerm(getScopeToUse(termsArray, realFolder, ioService.offline), realFolder);
- }
- }
- }
- else
- {
- viewDebug ("in createSearchTermsWithList, adding scope term for selected folder\n");
- gSearchSession.addScopeTerm(getScopeToUse(termsArray, selectedFolder, ioService.offline), selectedFolder);
- }
-
- // add each item in termsArray to the search session
- for (i = 0; i < termsArray.Count(); ++i)
- gSearchSession.appendTerm(termsArray.GetElementAt(i).QueryInterface(Components.interfaces.nsIMsgSearchTerm));
-}
-
-function getScopeToUse(aTermsArray, aFolderToSearch, aIsOffline)
-{
- if (aIsOffline || aFolderToSearch.server.type != 'imap')
- return nsMsgSearchScope.offlineMail;
-
- var scopeToUse = gSearchInput && gSearchInput.searchMode == kQuickSearchBody && !gSearchInput.showingSearchCriteria
- ? nsMsgSearchScope.onlineMail : nsMsgSearchScope.offlineMail;
-
- // it's possible one of our search terms may require us to use an online mail scope (such as imap body searches)
- for (var i = 0; scopeToUse != nsMsgSearchScope.onlineMail && i < aTermsArray.Count(); i++)
- if (aTermsArray.GetElementAt(i).QueryInterface(Components.interfaces.nsIMsgSearchTerm).attrib == nsMsgSearchAttrib.Body)
- scopeToUse = nsMsgSearchScope.onlineMail;
-
- return scopeToUse;
-}
-
-function createSearchTerms()
-{
- var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
- var nsMsgSearchAttrib = Components.interfaces.nsMsgSearchAttrib;
- var nsMsgSearchOp = Components.interfaces.nsMsgSearchOp;
-
- // create an i supports array to store our search terms
- var searchTermsArray = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
- var selectedFolder = GetThreadPaneFolder();
-
- // implement | for QS
- // does this break if the user types "foo|bar" expecting to see subjects with that string?
- // I claim no, since "foo|bar" will be a hit for "foo" || "bar"
- // they just might get more false positives
- if (!gSearchInput.showingSearchCriteria) // ignore the text box value if it's just showing the search criteria string
- {
- var termList = gSearchInput.value.split("|");
- for (var i = 0; i < termList.length; i ++)
- {
- // if the term is empty, skip it
- if (termList[i] == "")
- continue;
-
- // create, fill, and append the subject term
- var term;
- var value;
-
- // if our search criteria is subject or subject|from then add a term for the subject
- if (gSearchInput.searchMode == kQuickSearchSubject ||
- gSearchInput.searchMode == kQuickSearchFromOrSubject ||
- gSearchInput.searchMode == kQuickSearchRecipientOrSubject)
- {
- term = gSearchSession.createTerm();
- value = term.value;
- value.str = termList[i];
- term.value = value;
- term.attrib = nsMsgSearchAttrib.Subject;
- term.op = nsMsgSearchOp.Contains;
- term.booleanAnd = false;
- searchTermsArray.AppendElement(term);
- }
-
- if (gSearchInput.searchMode == kQuickSearchBody)
- {
- // what do we do for news and imap users that aren't configured for offline use?
- // in these cases the body search will never return any matches. Should we try to
- // see if body is a valid search scope in this particular case before doing the search?
- // should we switch back to a subject/from search behind the scenes?
- term = gSearchSession.createTerm();
- value = term.value;
- value.str = termList[i];
- term.value = value;
- term.attrib = nsMsgSearchAttrib.Body;
- term.op = nsMsgSearchOp.Contains;
- term.booleanAnd = false;
- searchTermsArray.AppendElement(term);
- }
-
- // create, fill, and append the from (or recipient) term
- if (gSearchInput.searchMode == kQuickSearchFrom || gSearchInput.searchMode == kQuickSearchFromOrSubject)
- {
- term = gSearchSession.createTerm();
- value = term.value;
- value.str = termList[i];
- term.value = value;
- term.attrib = nsMsgSearchAttrib.Sender;
- term.op = nsMsgSearchOp.Contains;
- term.booleanAnd = false;
- searchTermsArray.AppendElement(term);
- }
-
- // create, fill, and append the recipient
- if (gSearchInput.searchMode == kQuickSearchRecipient ||
- gSearchInput.searchMode == kQuickSearchRecipientOrSubject)
- {
- term = gSearchSession.createTerm();
- value = term.value;
- value.str = termList[i];
- term.value = value;
- term.attrib = nsMsgSearchAttrib.ToOrCC;
- term.op = nsMsgSearchOp.Contains;
- term.booleanAnd = false;
- searchTermsArray.AppendElement(term);
- }
- }
- }
-
- // now append the default view or virtual folder criteria to the quick search
- // so we don't lose any default view information
- viewDebug("gDefaultSearchViewTerms = " + gDefaultSearchViewTerms + "gVirtualFolderTerms = " + gVirtualFolderTerms +
- "gXFVirtualFolderTerms = " + gXFVirtualFolderTerms + "\n");
- var defaultSearchTerms = (gDefaultSearchViewTerms || gVirtualFolderTerms || gXFVirtualFolderTerms);
- if (defaultSearchTerms)
- {
- var isupports = null;
- var searchTerm;
- var termsArray = defaultSearchTerms.QueryInterface(Components.interfaces.nsISupportsArray);
- for (i = 0; i < termsArray.Count(); i++)
- {
- isupports = termsArray.GetElementAt(i);
- searchTerm = isupports.QueryInterface(Components.interfaces.nsIMsgSearchTerm);
- searchTermsArray.AppendElement(searchTerm);
- }
- }
-
- createSearchTermsWithList(searchTermsArray);
-
- // now that we've added the terms, clear out our input array
- searchTermsArray.Clear();
-}
-
-function onSearchStop()
-{
- gSearchSession.interruptSearch();
-}
function onSearchKeyPress()
{
if (gSearchInput.showingSearchCriteria)
gSearchInput.showingSearchCriteria = false;
}
function onSearchInputFocus(event)
{
GetSearchInput();
// search bar has focus, ...clear the showing search criteria flag
if (gSearchInput.showingSearchCriteria)
{
gSearchInput.value = "";
gSearchInput.showingSearchCriteria = false;
}
-
+
if (gIgnoreFocus) // got focus via mouse click, don't need to anything else
gIgnoreFocus = false;
else
gSearchInput.select();
}
function onSearchInputMousedown(event)
{
GetSearchInput();
- if (gSearchInput.hasAttribute("focused"))
+ if (gSearchInput.hasAttribute("focused"))
// If the search input is focused already, ignore the click so that
// onSearchInputBlur does nothing.
gIgnoreClick = true;
- else
+ else
{
gIgnoreFocus = true;
gIgnoreClick = false;
}
}
function onSearchInputClick(event)
{
@@ -587,67 +195,59 @@ function ClearQSIfNecessary()
{
if (!gSearchInput || gSearchInput.showingSearchCriteria)
return;
gSearchInput.setSearchCriteriaText();
}
function Search(str)
{
- viewDebug("in Search str = " + str + "gSearchInput.showingSearchCriteria = " + gSearchInput.showingSearchCriteria + "\n");
if (gSearchInput.showingSearchCriteria && str != "")
return;
- if (str != gSearchInput.value)
- {
- gQSViewIsDirty = true;
- viewDebug("in Search(), setting gQSViewIsDirty true\n");
- }
-
gSearchInput.value = str; //on input does not get fired for some reason
onEnterInSearchBar();
}
// helper methods for the quick search drop down menu
function changeQuickSearchMode(aMenuItem)
{
- viewDebug("changing quick search mode\n");
// extract the label and set the search input to match it
var oldSearchMode = gSearchInput.searchMode;
gSearchInput.searchMode = aMenuItem.value;
if (gSearchInput.value == "" || gSearchInput.showingSearchCriteria)
{
gSearchInput.showingSearchCriteria = true;
- if (gSearchInput.value) //
+ if (gSearchInput.value) //
gSearchInput.setSearchCriteriaText();
}
-
+
// if the search box is empty, set showing search criteria to true so it shows up when focus moves out of the box
- if (!gSearchInput.value)
+ if (!gSearchInput.value)
gSearchInput.showingSearchCriteria = true;
else if (gSearchInput.showingSearchCriteria) // if we are showing criteria text and the box isn't empty, change the criteria text
- gSearchInput.setSearchCriteriaText();
+ gSearchInput.setSearchCriteriaText();
else if (oldSearchMode != gSearchInput.searchMode) // the search mode just changed so we need to redo the quick search
onEnterInSearchBar();
}
function saveViewAsVirtualFolder()
{
gFolderTreeController.newVirtualFolder(gSearchInput.value,
gSearchSession.searchTerms);
}
function InitQuickSearchPopup()
{
// disable the create virtual folder menu item if the current radio
// value is set to Find in message since you can't really create a VF from find
// in message
-
- GetSearchInput();
+
+ GetSearchInput();
if (!gSearchInput ||gSearchInput.value == "" || gSearchInput.showingSearchCriteria)
document.getElementById('quickSearchSaveAsVirtualFolder').setAttribute('disabled', 'true');
else
document.getElementById('quickSearchSaveAsVirtualFolder').removeAttribute('disabled');
}
/**
* If switching from an "incoming" (Inbox, etc.) type of mail folder,
--- a/mail/base/content/selectionsummaries.js
+++ b/mail/base/content/selectionsummaries.js
@@ -64,37 +64,16 @@ function loadSelectionSummaryStrings() {
strBundleService = strBundleService.QueryInterface(Components.interfaces.nsIStringBundleService);
for (let [name, value] in Iterator(gSelectionSummaryStrings))
gSelectionSummaryStrings[name] = typeof value == "string" ?
getStr(value) : value.map(gSelectionSummaryStrings);
}
loadSelectionSummaryStrings();
-/**
- * pickMessagePane is the toggle to figure out whether to use the standard
- * message pane used to display message bodies (& headers), or whether to
- * display an HTML iframe used for the multiple message summaries.
- *
- * @param visiblePaneId
- * the ID of the pane that we want to make be the visible one.
- * @return the DOM element corresponding to the selected pane.
- *
- */
-function pickMessagePane(visiblePaneId) {
- var paneIds = ['singlemessage', 'multimessage'];
- // when we want to do folder and account summaries, we just need to add
- // XUL elements for them, and add them to this list.
- // 'foldersummary', 'accountsummary'];
- for (let [,paneId] in Iterator(paneIds))
- document.getElementById(paneId).hidden = paneId != visiblePaneId;
-
- return document.getElementById(visiblePaneId);
-}
-
// Ah, wouldn't it be nice if there was platform code to do the following...
/**
* the equivalent of jQuery's addClass. Avoids duplicates, nothing fancy.
*
* @param node
* any old DOM node
* @param classname
@@ -143,33 +122,30 @@ function _mm_removeClass(node, classname
* It uses the same multimessage iframe as ThreadSummary, so both it
* and ThreadSummary should be careful to clean up the other's work
* before inserting their DOM nodes into the frame.
*
* There's a two phase process: build the framework based on what's available
* from the msgHdr itself, and then spawn an aysnc Gloda query which will
* fetch the snippets, tags, etc.
*
- * @param msgURIs
- * array of message URIs
+ * @param aMessages
+ * Array of message headers.
*/
-function MultiMessageSummary(msgURIs) {
- this._msgURIs = msgURIs;
+function MultiMessageSummary(aMessages) {
+ this._msgHdrs = aMessages;
}
MultiMessageSummary.prototype = {
init: function() {
this._msgTagService = Components.classes["@mozilla.org/messenger/tagservice;1"].
getService(Components.interfaces.nsIMsgTagService);
- this._msgHdrs = new Array();
this._glodaQueries = [];
this._msgNodes = {};
- for (var i = 0; i < this._msgURIs.length; ++i)
- this._msgHdrs.push(messenger.msgHdrFromURI(this._msgURIs[i]));
this.summarize();
},
/**
* Given a msgHdr, return a list of tag objects. This function
* just does the messy work of understanding how tags are
* stored in nsIMsgDBHdrs. It would be a good candidate for
@@ -212,17 +188,17 @@ MultiMessageSummary.prototype = {
senderName = senderName.slice(1, -1);
return senderName;
},
/**
* Fill in the summary pane describing the selected messages
**/
summarize: function() {
- let htmlpane = pickMessagePane('multimessage');
+ let htmlpane = document.getElementById('multimessage');
// First, we group the messages in threads.
// count threads
let threads = {};
let numThreads = 0;
let headerParser = Components.classes["@mozilla.org/messenger/headerparser;1"].
getService(Components.interfaces.nsIMsgHeaderParser);
for (let [,msgHdr] in Iterator(this._msgHdrs))
{
@@ -235,18 +211,19 @@ MultiMessageSummary.prototype = {
}
}
// set the heading based on the number of messages & threads
let heading = htmlpane.contentDocument.getElementById('heading');
_mm_addClass(heading, "heading");
_mm_addClass(heading, "info");
- let numMessages = this._msgURIs.length;
- let messagesTitle = PluralForm.get(numMessages, gSelectionSummaryStrings["NConversations"]).replace('#1', numThreads);
+ let messagesTitle =
+ PluralForm.get(numThreads, gSelectionSummaryStrings["NConversations"])
+ .replace("#1", numThreads);
heading.innerHTML = messagesTitle;
// clear the messages list
let messagesElt = htmlpane.contentDocument.getElementById('messagelist');
while (messagesElt.firstChild)
messagesElt.removeChild(messagesElt.firstChild);
@@ -259,17 +236,17 @@ MultiMessageSummary.prototype = {
count += 1;
if (count > MAXCOUNT) {
maxCountExceeded = true;
break;
}
let countUnread = 0;
let countStarred = 0;
let header, countNode;
-
+
// we'll mark the thread unread if any messages in it are unread
for (let [, msgHdr] in Iterator(msgs)) {
if (! msgHdr.isRead)
countUnread += 1;
if (msgHdr.isFlagged)
countStarred += 1;
}
@@ -324,30 +301,30 @@ MultiMessageSummary.prototype = {
if (meta.author)
authorNode.innerHTML = escapeXMLchars(meta.author);
});
// get the subject node.
let subjectNode = msgNode.getElementsByClassName("subject")[0];
subjectNode.msgs = msgs;
subjectNode.addEventListener("click", function() {
- selectMultipleMessagesByMsgHdr(this.msgs);
+ gFolderDisplay.selectMessages(this.msgs);
}, true);
let tagsNode = msgNode.getElementsByClassName("tags")[0];
while (tagsNode.firstChild)
tagsNode.removeChild(tagsNode.firstChild);
this._addTagNodes(msgs, tagsNode);
for each (msgHdr in msgs) {
this._msgNodes[msgHdr.messageKey + msgHdr.folder.URI] = msgNode;
}
messagesElt.appendChild(msgNode);
}
this.computeSize(htmlpane);
- this.notifyMaxCountExceeded(numMessages, MAXCOUNT);
+ this.notifyMaxCountExceeded(this._msgHdrs.length, MAXCOUNT);
this._glodaQueries.push(Gloda.getMessageCollectionForHeaders(this._msgHdrs, this));
},
/**
* clear out the tagsnode, and fill in appropriately for the union of
* tags in the specified messags
*/
@@ -388,17 +365,17 @@ MultiMessageSummary.prototype = {
sizeText = replaceInsert(sizeText, 2, unit);
htmlpane.contentDocument.getElementById('size').innerHTML = sizeText;
},
/** compute the size of the messages in the selection and display it
* in the element of id "size"
**/
notifyMaxCountExceeded: function(numMessages, maxCount) {
- let htmlpane = pickMessagePane('multimessage');
+ let htmlpane = document.getElementById('multimessage');
let notice = htmlpane.contentDocument.getElementById('notice');
if (numMessages > maxCount)
{
let noticeText = gSelectionSummaryStrings.noticeText;
noticeText = replaceInsert(noticeText, 1, numMessages);
noticeText = replaceInsert(noticeText, 2, maxCount);
notice.innerHTML = noticeText;
_mm_removeClass(notice, 'hidden');
@@ -436,24 +413,24 @@ MultiMessageSummary.prototype = {
// thread (and hence DOM node), so we need to detect when we get the first
// item for a particular DOM node, stash the preexisting status of that DOM
// node, an only do transitions if the items warrant it.
let headerNode = this._msgNodes[domkey];
if (! headerNode.flags) {
headerNode.flags = {};
knownMessageNodes.push(headerNode);
}
-
+
if (! glodaMsg.read)
headerNode.flags['unread'] = true;
if (glodaMsg.starred)
headerNode.flags['starred'] = true;
// for tags, there's a minor problem in that if _some_ of the items in a
- // thread got modified
+ // thread got modified
let key = messageKey + glodaMsg.folder.uri;
let tagsNode = headerNode.getElementsByClassName('tags')[0];
while (tagsNode.firstChild)
tagsNode.removeChild(tagsNode.firstChild);
this._addTagNodes([msg.folderMessage for each ([i,msg] in Iterator(aItems))],
tagsNode);
}
@@ -468,17 +445,17 @@ MultiMessageSummary.prototype = {
_mm_removeClass(headerNode, "starred");
headerNode.flags = null;
}
},
onQueryCompleted: function(aCollection) {
/* if we need something that's just available from GlodaMessages,
this is where we'll get it initially */
- return;
+ return;
}
}
/**
* the ThreadSummary class is responsible for populating the message pane
* with a reasonable summary of a set of messages that are are in a single
* thread.
@@ -486,60 +463,58 @@ MultiMessageSummary.prototype = {
* It uses the same multimessage iframe as MultiMessageSummary, so both it
* and MultiMessageSummary should be careful to clean up the other's work
* before inserting their DOM nodes into the frame.
*
* There's a two phase process: build the framework based on what's available
* from the msgHdr itself, and then spawn an aysnc Gloda query which will
* fetch the snippets, tags, etc.
*
- * @param msgURIs
- * array of message URIs
+ * @param messages
+ * array of message headers
*/
-function ThreadSummary(msgURIs)
+function ThreadSummary(messages)
{
- this._msgURIs = msgURIs;
+ this._msgHdrs = messages;
}
ThreadSummary.prototype = {
__proto__: MultiMessageSummary.prototype,
summarize: function() {
this._msgNodes = {};
- let htmlpane = pickMessagePane('multimessage');
+ let htmlpane = document.getElementById('multimessage');
- let firstMsgHdr = messenger.msgHdrFromURI(this._msgURIs[0]);
- let numMessages = this._msgURIs.length;
+ let firstMsgHdr = this._msgHdrs[0];
+ let numMessages = this._msgHdrs.length;
let subject = (firstMsgHdr.mime2DecodedSubject || gSelectionSummaryStrings["noSubject"])
+ " "
+ PluralForm.get(numMessages, gSelectionSummaryStrings["Nmessages"]).replace('#1', numMessages);
let heading = htmlpane.contentDocument.getElementById('heading');
heading.setAttribute("class", "heading");
heading.innerHTML = escapeXMLchars(subject);
let messagesElt = htmlpane.contentDocument.getElementById('messagelist');
while (messagesElt.firstChild)
messagesElt.removeChild(messagesElt.firstChild);
let headerParser = Components.classes["@mozilla.org/messenger/headerparser;1"]
.getService(Components.interfaces.nsIMsgHeaderParser);
- let msgHdrs = new Array();
let count = 0;
const MAXCOUNT = 100;
let maxCountExceeded = false;
- for (let i = 0; i < this._msgURIs.length; ++i) {
+ for (let i = 0; i < numMessages; ++i) {
count += 1;
if (count > MAXCOUNT) {
maxCountExceeded = true;
break;
}
- let msgHdr = messenger.msgHdrFromURI(this._msgURIs[i]);
- msgHdrs.push(msgHdr);
+ let msgHdr = this._msgHdrs[i];
let msg_classes = "message ";
if (! msgHdr.isRead)
msg_classes += " unread";
if (msgHdr.isFlagged)
msg_classes += " starred";
let senderName = headerParser.extractHeaderAddressName(msgHdr.mime2DecodedAuthor);
@@ -580,17 +555,17 @@ ThreadSummary.prototype = {
for each (let [,tag] in Iterator(tags)) {
let tagNode = tagsNode.ownerDocument.createElement('span');
// see tagColors.css
let colorClass = "blc-" + this._msgTagService.getColorForKey(tag.key).substr(1);
_mm_addClass(tagNode, "tag " + tag.tag + " " + colorClass);
tagNode.innerHTML = tag.tag;
tagsNode.appendChild(tagNode);
}
-
+
let sender = msgNode.getElementsByClassName("sender")[0];
sender.msgHdr = msgHdr;
sender.addEventListener("click", function(e) {
// if the msg is the first message in a collapsed thread, we need to
// uncollapse it.
let origRowCount = gDBView.rowCount;
let viewIndex = gDBView.findIndexOfMsgHdr(e.target.msgHdr, true);
gDBView.selectFolderMsgByKey(this.folder, this.msgKey);
@@ -599,93 +574,64 @@ ThreadSummary.prototype = {
}, true);
sender.folder = msgHdr.folder;
sender.msgKey = msgHdr.messageKey;
this._msgNodes[key] = msgNode;
messagesElt.appendChild(msgNode);
}
-
+
// stash somewhere so it doesn't get GC'ed
- this._glodaQueries.push(Gloda.getMessageCollectionForHeaders(msgHdrs, this));
+ this._glodaQueries.push(Gloda.getMessageCollectionForHeaders(this._msgHdrs, this));
this.notifyMaxCountExceeded(numMessages, MAXCOUNT);
this.computeSize(htmlpane);
}
-}
+};
// We use a global to prevent GC of gloda collection (and we reuse it to prevent
// leaks). Without a global, the GC is aggressive enough that the gloda query
// is gone before it returns.
var gSummary;
/**
- * Given an array of message URIs which are all in the
+ * Given an array of messages which are all in the
* same thread, summarize them.
*
- * @param selectedMsgUris
- * array of message URIs
+ * @param aSelectedMessages
+ * array of message headers
*/
-function summarizeThread(selectedMsgUris)
+function summarizeThread(aSelectedMessages)
{
- if (selectedMsgUris.length == 0)
+ if (aSelectedMessages.length == 0)
return;
- gSummary = new ThreadSummary(selectedMsgUris);
+ gSummary = new ThreadSummary(aSelectedMessages);
gSummary.init();
}
/**
* Given an array of message URIs, cause the message pane
* to display a summary of them.
*
- * @param selectedMsgUris
- * array of message URIs
+ * @param aSelectedMessages
+ * array of message headers
*/
-function summarizeMultipleSelection(selectedMsgUris)
+function summarizeMultipleSelection(aSelectedMessages)
{
- if (selectedMsgUris.length == 0)
+ if (aSelectedMessages.length == 0)
return;
- gSummary = new MultiMessageSummary(selectedMsgUris);
+ gSummary = new MultiMessageSummary(aSelectedMessages);
gSummary.init();
}
/**
- * Given an array of nsMsgHdrs, select all of them. This will uncollapse
- * threads that are collapsed as necessary.
- *
- * @param msgHdrs
- * array of msgHdr's
- */
-function selectMultipleMessagesByMsgHdr(msgHdrs)
-{
- let treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
- if (msgHdrs.length == 1) {
- gDBView.selectFolderMsgByKey(msgHdrs[0].folder, msgHdrs[0].messageKey);
- } else {
- let treeSelection = treeView.selection;
- treeSelection.clearSelection();
- for (let [, msgHdr] in Iterator(msgHdrs))
- {
- let viewIndex = gDBView.findIndexOfMsgHdr(msgHdr, false);
- let thread = gDBView.getThreadContainingIndex(viewIndex);
- let flags = gDBView.getFlagsAt(viewIndex);
-
- if (flags & Components.interfaces.nsMsgMessageFlags.Elided)
- treeView.toggleOpenState(viewIndex);
-
- treeSelection.rangedSelect(viewIndex, viewIndex, true);
- }
- }
-}
-
-/**
* Helper function to generate a localized "friendly" representation of
* time relative to the present. If the time input is "today", it returns
* a string corresponding to just the time. If it's yesterday, it returns
* "yesterday" (localized). If it's in the last week, it returns the day
* of the week. If it's before that, it returns the date.
*
* @param time
* the time (better be in the past!)
--- a/mail/base/content/specialTabs.js
+++ b/mail/base/content/specialTabs.js
@@ -147,30 +147,40 @@ var specialTabs = {
switch (aCommand) {
case "cmd_fullZoomReduce":
case "cmd_fullZoomEnlarge":
case "cmd_fullZoomReset":
case "cmd_fullZoomToggle":
case "cmd_find":
case "cmd_findAgain":
case "cmd_findPrevious":
+ case "cmd_printSetup":
+ case "cmd_print":
+ case "button_print":
+ // XXX print preview not currently supported - bug 497994 to implement.
+ // case "cmd_printpreview":
return true;
default:
return false;
}
},
isCommandEnabled: function isCommandEnabled(aTab, aCommand) {
switch (aCommand) {
case "cmd_fullZoomReduce":
case "cmd_fullZoomEnlarge":
case "cmd_fullZoomReset":
case "cmd_fullZoomToggle":
case "cmd_find":
case "cmd_findAgain":
case "cmd_findPrevious":
+ case "cmd_printSetup":
+ case "cmd_print":
+ case "button_print":
+ // XXX print preview not currently supported - bug 497994 to implement.
+ // case "cmd_printpreview":
return true;
default:
return false;
}
},
doCommand: function isCommandEnabled(aTab, aCommand) {
switch (aCommand) {
case "cmd_fullZoomReduce":
@@ -189,16 +199,26 @@ var specialTabs = {
aTab.panel.childNodes[1].onFindCommand();
break;
case "cmd_findAgain":
aTab.panel.childNodes[1].onFindAgainCommand(false);
break;
case "cmd_findPrevious":
aTab.panel.childNodes[1].onFindAgainCommand(true);
break;
+ case "cmd_printSetup":
+ PrintUtils.showPageSetup();
+ break;
+ case "cmd_print":
+ PrintUtils.print();
+ break;
+ // XXX print preview not currently supported - bug 497994 to implement.
+ //case "cmd_printpreview":
+ // PrintUtils.printPreview();
+ // break;
}
},
getBrowser: function getBrowser(aTab) {
return aTab.panel.firstChild;
}
},
/**
--- a/mail/base/content/tabmail.xml
+++ b/mail/base/content/tabmail.xml
@@ -1,46 +1,46 @@
<?xml version="1.0"?>
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is tab email
-#
-# The Initial Developer of the Original Code is
-# David Bienvenu <bienvenu@nventure.com>.
-# Portions created by the Initial Developer are Copyright (C) 2007
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# Scott MacGregor <mscott@mozilla.org>
-# Andrew Sutherland <asutherland@asutherland.org>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
+<!-- ***** BEGIN LICENSE BLOCK *****
+ - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ -
+ - The contents of this file are subject to the Mozilla Public License Version
+ - 1.1 (the "License"); you may not use this file except in compliance with
+ - the License. You may obtain a copy of the License at
+ - http://www.mozilla.org/MPL/
+ -
+ - Software distributed under the License is distributed on an "AS IS" basis,
+ - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ - for the specific language governing rights and limitations under the
+ - License.
+ -
+ - The Original Code is tab email
+ -
+ - The Initial Developer of the Original Code is
+ - David Bienvenu <bienvenu@nventure.com>.
+ - Portions created by the Initial Developer are Copyright (C) 2007
+ - the Initial Developer. All Rights Reserved.
+ -
+ - Contributor(s):
+ - Scott MacGregor <mscott@mozilla.org>
+ - Andrew Sutherland <asutherland@asutherland.org>
+ -
+ - Alternatively, the contents of this file may be used under the terms of
+ - either the GNU General Public License Version 2 or later (the "GPL"), or
+ - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ - in which case the provisions of the GPL or the LGPL are applicable instead
+ - of those above. If you wish to allow use of your version of this file only
+ - under the terms of either the GPL or the LGPL, and not to allow others to
+ - use your version of this file under the terms of the MPL, indicate your
+ - decision by deleting the provisions above and replace them with the notice
+ - and other provisions required by the GPL or the LGPL. If you do not delete
+ - the provisions above, a recipient may use your version of this file under
+ - the terms of any one of the MPL, the GPL or the LGPL.
+ -
+ - ***** END LICENSE BLOCK ***** -->
<!DOCTYPE bindings [
<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
%messengerDTD;
<!ENTITY % tabMailDTD SYSTEM "chrome://messenger/locale/tabmail.dtd" >
%tabMailDTD;
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
@@ -71,28 +71,52 @@
- 1) Code that wants to open new tabs.
- 2) Code that wants to contribute one or more varieties of tabs.
- 3) Code that wants to monitor to know when the active tab changes.
-
- Consumer code should use the following methods:
- * openTab(aTabModeName, arguments...): Open a tab of the given "mode",
- passing the provided arguments. The tab type author should tell you
- the modes they implement and the required/optional arguments.
+ - * closeTab(aOptionalTabIndexInfoOrTabNode): If no argument is provided,
+ - the current tab is closed. If an argument is provided, it can be
+ - a tab index, a tab info object, or the tabmail-tab bound element
+ - that _is_ the tab. Some tabs cannot be closed, in which case this
+ - will do nothing.
+ - * switchToTab(aTabIndexInfoOrTabNode): Switch to the tab by providing
+ - a tab index, tab info object, or tab node (tabmail-tab bound
+ - element.) You can also just poke at tabmail.tabContainer and its
+ - selectedIndex and selectedItem properties.
+ - Less-friendly consumer methods:
+ - * removeCurrentTab(): Close the current tab.
+ - * removeTabByNode(aTabElement): Close the tab whose tabmail-tab bound
+ - element is passed in.
+ - Changing the currently displayed tab is accomplished by changing
+ - tabmail.tabContainer's selectedIndex or selectedItem property.
+ -
+ - Code that lives in a tab should use the following methods:
- * setTabTitle([aOptionalTabInfo]): Tells us that the title of the current
- tab (if no argument is provided) or provided tab needs to be updated.
- This will result in a call to the tab mode's logic to update the title.
- In the event this is not for the current tab, the caller is responsible
- for ensuring that the underlying tab mode is capable of providing a tab
- title when it is in the background. (The is currently not the case for
- "folder" and "mail" modes because of their implementation.)
- - * removeCurrentTab(): Close the current tab.
- - * removeTabByNode(aTabElement): Close the tab whose tabmail-tab bound
- - element is passed in.
- - Changing the currently displayed tab is accomplished by changing
- - tabmail.tabContainer's selectedIndex or selectedItem property.
+ - * setTabBusy(aTabNode, aBusyState): Tells us that the tab in question
+ - is now busy or not busy. "Busy" means that it is occupied and
+ - will not be able to respond to you until it is no longer busy.
+ - This impacts the cursor display, as well as potentially
+ - providing tab display hints.
+ - * setTabThinking(aTabNode, aThinkingState): Tells us that the
+ - tab in question is now thinking or not thinking. "Thinking" means
+ - that the tab is involved in some ongoing process but you can still
+ - interact with the tab while it is thinking. A search would be an
+ - example of thinking. This impacts spinny-thing feedback as well as
+ - potential providing tab display hints. aThinkingState may be a
+ - boolean or a localized string explaining what you are thinking about.
-
- Tab contributing code should define a tab type object and register it
- with us by calling registerTabType. Each tab type can provide multiple
- tab modes. The rationale behind this organization is that Thunderbird
- historically/currently uses a single 3-pane view to display both
- three-pane folder browsing and single message browsing across multiple
- tabs. Each tab type has the ability to use a single tab panel for all
- of its display needs. So Thunderbird's "mail" tab type covers both the
@@ -232,16 +256,22 @@
window.controllers.insertControllerAt(0, this);
</constructor>
<destructor>
window.controllers.removeController(this);
</destructor>
<field name="currentTabInfo">
null
</field>
+ <!-- Temporary field that only has a non-null value during a call to
+ openTab, and whose value is the currentTabInfo of the tab that was
+ open when we received the call to openTab. -->
+ <field name="_mostRecentTabInfo">
+ null
+ </field>
<field name="tabTypes" readonly="true">
new Object()
</field>
<field name="tabModes" readonly="true">
new Object()
</field>
<field name="defaultTabMode">
null
@@ -289,33 +319,76 @@
</method>
<method name="unregisterTabMonitor">
<parameter name="aTabMonitor"/>
<body><![CDATA[
if (this.tabMonitors.indexOf(aTabMonitor) >= 0)
this.tabMonitors.splice(this.tabMonitors.indexOf(aTabMonitor), 1);
]]></body>
</method>
+ <!-- Given an index, tab node or tab info object, return a tuple of
+ [iTab, tab info dictionary, tab DOM node]. If
+ aTabIndexNodeOrInfo is not specified and aDefaultToCurrent is
+ true, the current tab will be returned. Otherwise, an
+ exception will be thrown.
+ -->
+ <method name="_getTabContextForTabbyThing">
+ <parameter name="aTabIndexNodeOrInfo"/>
+ <parameter name="aDefaultToCurrent"/>
+ <body><![CDATA[
+ let iTab, tab, tabNode;
+ if (!aTabIndexNodeOrInfo) {
+ if (!aDefaultToCurrent)
+ throw Exception("You need to specify a tab!");
+ iTab = this.tabContainer.selectedIndex;
+ return [iTab, this.tabInfo[iTab],
+ this.tabContainer.childNodes[iTab]];
+ }
+
+ if (typeof(aTabIndexNodeOrInfo) == "number") {
+ iTab = aTabIndexNodeOrInfo;
+ tabNode = this.tabContainer.childNodes[iTab];
+ tab = this.tabInfo[iTab];
+ }
+ else if (aTabIndexNodeOrInfo.tagName == "tab") {
+ tabNode = aTabIndexNodeOrInfo;
+ iTab = this.tabContainer.getIndexOfItem(tabNode);
+ tab = this.tabInfo[iTab];
+ }
+ else {
+ tab = aTabIndexNodeOrInfo;
+ iTab = this.tabInfo.indexOf(tab);
+ tabNode = (iTab >= 0) ? this.tabContainer.childNodes[iTab] : null;
+ }
+
+ return [iTab, tab, tabNode];
+ ]]></body>
+ </method>
<method name="openFirstTab">
<body><![CDATA[
// From the moment of creation, our XBL binding already has a visible
// tab. We need to create a tab information structure for this tab.
// In the process we also generate a synthetic tab title changed
// event to ensure we have an accurate title. We assume the tab
// contents will set themselves up correctly.
if (this.tabInfo.length == 0) {
- let firstTab = {mode: this.defaultTabMode, canClose: false};
+ let firstTab = {mode: this.defaultTabMode, busy: false,
+ canClose: false};
firstTab.mode.tabs.push(firstTab);
-
+
this.tabInfo[0] = this.currentTabInfo = firstTab;
- this.setTabTitle(firstTab);
+
+ let tabOpenFirstFunc = firstTab.mode.openFirstTab ||
+ firstTab.mode.tabType.openFirstTab;
+ tabOpenFirstFunc.call(firstTab.mode.tabType, firstTab);
+ this.setTabTitle(null);
if (this.tabMonitors.length) {
for each (let [i, tabMonitor] in Iterator(this.tabMonitors))
- tabMonitor.onTabSwitched(tab, null);
+ tabMonitor.onTabSwitched(firstTab, null);
}
}
]]></body>
</method>
<method name="openTab">
<parameter name="aTabModeName"/>
<body>
<![CDATA[
@@ -339,37 +412,36 @@
this.selectTabByIndex(null, tabIndex);
return;
}
}
// we need to save the state before it gets corrupted
this.saveCurrentTabState();
- let tab = {mode: tabMode, canClose: true};
+ let tab = {mode: tabMode, busy: false, canClose: true};
tabMode.tabs.push(tab);
var t = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"tab");
t.setAttribute("crop", "end");
t.maxWidth = this.tabContainer.mTabMaxWidth;
t.minWidth = this.tabContainer.mTabMinWidth;
t.width = 0;
t.setAttribute("flex", "100");
t.setAttribute("validate", "never");
t.className = "tabmail-tab";
this.tabContainer.appendChild(t);
- this.tabContainer.appendChild(t);
if (this.tabContainer.collapsed) {
this.tabContainer.collapsed = false;
this.tabContainer.adjustTabstrip();
}
- let oldTab = this.currentTabInfo;
+ let oldTab = this._mostRecentTabInfo = this.currentTabInfo;
// the order of the following statements is important
this.currentTabInfo = tab;
this.tabInfo[this.tabContainer.childNodes.length - 1] = tab;
// this has a side effect of calling updateCurrentTab, but our
// setting currentTabInfo above will cause it to take no action.
this.tabContainer.selectedIndex =
this.tabContainer.childNodes.length - 1;
@@ -395,26 +467,33 @@
let args = [tab].concat(Array.prototype.slice.call(arguments, 1));
tabOpenFunc.apply(tab.mode.tabType, args);
if (this.tabMonitors.length) {
for each (let [i, tabMonitor] in Iterator(this.tabMonitors))
tabMonitor.onTabSwitched(tab, oldTab);
}
+ // clear _mostRecentTabInfo; we only needed it during the call to
+ // openTab.
+ this._mostRecentTabInfo = null;
+
t.setAttribute("label", tab.title);
let docTitle = tab.title;
-#ifndef XP_MACOSX
- docTitle += " - " + gBrandBundle.getString("brandFullName");
-#endif
+ if (!gPlatformOSX)
+ docTitle += " - " + gBrandBundle.getString("brandFullName");
document.title = docTitle;
// for styling purposes, apply the type to the tab...
t.setAttribute('type', tab.mode.type);
+
+ // Update the toolbar status - we don't need to do menus as they
+ // do themselves when we open them.
+ UpdateMailToolbar("tabmail");
]]></body>
</method>
<method name="selectTabByMode">
<parameter name="aTabModeName"/>
<body><![CDATA[
let tabMode = this.tabModes[aTabModeName];
if (tabMode.tabs.length) {
let desiredTab = tabMode.tabs[0];
@@ -437,60 +516,93 @@
}
if (aEvent) {
aEvent.preventDefault();
aEvent.stopPropagation();
}
]]></body>
</method>
+ <method name="getTabInfoForCurrentOrFirstModeInstance">
+ <parameter name="aTabMode"/>
+ <body><![CDATA[
+ /**
+ * If the current/most recent tab is of mode aTabModeName, return its
+ * tab info, otherwise return the tab info for the first tab of the
+ * given mode.
+ * You would want to use this method when you would like to mimic the
+ * settings of an existing instance of your mode. In such a case,
+ * it is reasonable to assume that if the 'current' tab was of the
+ * same mode that its settings should be used. Otherwise, we must
+ * fall back to another tab. We currently choose the first tab of
+ * the instance, because for the "folder" tab, it is the canonical tab.
+ * In other cases, having an MRU order and choosing the MRU tab might
+ * be more appropriate.
+ *
+ * @return the tab info object for the tab meeting the above criteria,
+ * or null if no such tab exists.
+ */
+ if (this._mostRecentTabInfo &&
+ this._mostRecentTabInfo.mode == aTabMode)
+ return this._mostRecentTabInfo;
+ else if (aTabMode.tabs.length)
+ return aTabMode.tabs[0];
+ else
+ return null;
+ ]]></body>
+ </method>
+ <method name="closeTab">
+ <parameter name="aOptTabIndexNodeOrInfo"/>
+ <body>
+ <![CDATA[
+ let [iTab, tab, tabNode] =
+ this._getTabContextForTabbyThing(aOptTabIndexNodeOrInfo, true);
+
+ if (!tab.canClose)
+ return;
+
+ let closeFunc = tab.mode.closeTab || tab.mode.tabType.closeTab;
+ closeFunc.call(tab.mode.tabType, tab);
+
+ this.tabInfo.splice(iTab, 1);
+ tab.mode.tabs.splice(tab.mode.tabs.indexOf(tab), 1);
+ this.tabContainer.removeChild(tabNode);
+ if (this.tabContainer.selectedIndex == -1)
+ this.tabContainer.selectedIndex =
+ (iTab == this.tabContainer.childNodes.length) ? iTab - 1 : iTab;
+ if (this.currentTabInfo == tab)
+ this.updateCurrentTab();
+
+ if (tab.panel) {
+ this.panelContainer.removeChild(tab.panel);
+ delete tab.panel;
+ }
+ if (this.tabContainer.childNodes.length == 1 &&
+ this.tabContainer.mAutoHide)
+ this.tabContainer.collapsed = true;
+ ]]>
+ </body>
+ </method>
<method name="closeTabs">
<body>
<![CDATA[
for (var i = 0; i < this.tabInfo.length; i++) {
let tab = this.tabInfo[i];
let tabCloseFunc = tab.mode.closeTab || tab.mode.tabType.closeTab;
tabCloseFunc.call(tab.mode.tabType, tab);
}
]]>
</body>
</method>
<method name="removeTabByNode">
<parameter name="aTabNode"/>
<body>
<![CDATA[
- // Find and locate the tab in our list.
- let iTab, numTabs = this.tabContainer.childNodes.length;
- for (iTab = 0; iTab < numTabs; iTab++)
- if (this.tabContainer.childNodes[iTab] == aTabNode)
- break;
- let tab = this.tabInfo[iTab];
-
- if (!tab.canClose)
- return;
-
- let closeFunc = tab.mode.closeTab || tab.mode.tabType.closeTab;
- closeFunc.call(tab.mode.tabType, tab);
-
- this.tabInfo.splice(iTab, 1);
- tab.mode.tabs.splice(tab.mode.tabs.indexOf(tab), 1);
- this.tabContainer.removeChild(aTabNode);
- if (this.tabContainer.selectedIndex == -1)
- this.tabContainer.selectedIndex = (iTab == --numTabs) ?
- iTab - 1 : iTab;
- if (this.currentTabInfo == tab)
- this.updateCurrentTab();
-
- if (tab.panel) {
- this.panelContainer.removeChild(tab.panel);
- delete tab.panel;
- }
- if (numTabs == 1 && this.tabContainer.mAutoHide)
- this.tabContainer.collapsed = true;
+ this.closeTab(aTabNode);
]]>
</body>
</method>
<!-- getBrowserForSelectedTab is required as some toolkit functions
require a getBrowser() function. -->
<method name="getBrowserForSelectedTab">
<body><![CDATA[
if (!this.currentTabInfo)
@@ -506,16 +618,27 @@
]]></body>
</method>
<method name="removeCurrentTab">
<body><![CDATA[
this.removeTabByNode(
this.tabContainer.childNodes[this.tabContainer.selectedIndex]);
]]></body>
</method>
+ <method name="switchToTab">
+ <parameter name="aTabIndexNodeOrInfo"/>
+ <body>
+ <![CDATA[
+ let [iTab, tab, tabNode] =
+ this._getTabContextForTabbyThing(aTabIndexNodeOrInfo, false);
+
+ this.tabContainer.selectedIndex = iTab;
+ ]]>
+ </body>
+ </method>
<!-- UpdateCurrentTab - called in response to changing the current tab -->
<method name="updateCurrentTab">
<body>
<![CDATA[
if (this.currentTabInfo != this.tabInfo[this.tabContainer.selectedIndex])
{
if (this.currentTabInfo)
this.saveCurrentTabState();
@@ -530,21 +653,29 @@
let showTabFunc = tab.mode.showTab || tab.mode.tabType.showTab;
showTabFunc.call(tab.mode.tabType, tab);
if (this.tabMonitors.length) {
for each (let [i, tabMonitor] in Iterator(this.tabMonitors))
tabMonitor.onTabSwitched(tab, oldTab);
}
+ // always update the cursor status when we switch tabs
+ SetBusyCursor(window, tab.busy);
+ // active tabs should not have the wasBusy attribute
+ this.tabContainer.selectedItem.removeAttribute("wasBusy");
+
+ // update the thinking status when we switch tabs
+ this._setActiveThinkingState(tab.thinking);
+ // active tabs should not have the wasThinking attribute
+ this.tabContainer.selectedItem.removeAttribute("wasThinking");
let docTitle = tab.title;
-#ifndef XP_MACOSX
- docTitle += " - " + gBrandBundle.getString("brandFullName");
-#endif
+ if (!gPlatformOSX)
+ docTitle += " - " + gBrandBundle.getString("brandFullName");
document.title = docTitle;
// Update the toolbar status - we don't need to do menus as they
// do themselves when we open them.
UpdateMailToolbar("tabmail");
}
]]>
</body>
@@ -571,64 +702,129 @@
if (event.button != 1 || event.target.localName != 'tab')
return;
this.removeTabByNode(event.target);
event.stopPropagation();
]]>
</body>
</method>
<method name="setTabTitle">
- <parameter name="aTab"/>
+ <parameter name="aTabNodeOrInfo"/>
<body>
<![CDATA[
- // First find the tab and its index.
- let tab;
- let index;
- if (aTab) {
- tab = aTab;
- for (index = 0; index < this.tabInfo.length; ++index) {
- if (tab == this.tabInfo[index])
- break;
- }
- }
- else {
- index = this.tabContainer.selectedIndex;
- tab = this.tabInfo[index];
- }
+ let [iTab, tab, tabNode] =
+ this._getTabContextForTabbyThing(aTabNodeOrInfo, true);
- // on startup, we may not have a tab...
if (tab)
{
let tabNode =
- this.tabContainer.childNodes[index];
+ this.tabContainer.childNodes[iTab];
let titleChangeFunc = tab.mode.onTitleChanged ||
tab.mode.tabType.onTitleChanged;
if (titleChangeFunc)
titleChangeFunc.call(tab.mode.tabType, tab, tabNode);
if (this.tabMonitors.length) {
- for each (let [index, tabMonitor] in Iterator(this.tabMonitors))
+ for each (let [, tabMonitor] in Iterator(this.tabMonitors))
tabMonitor.onTabTitleChanged(tab);
}
tabNode.setAttribute("label", tab.title);
// Update the window title if we're the displayed tab.
- if (index == this.tabContainer.selectedIndex) {
+ if (iTab == this.tabContainer.selectedIndex) {
let docTitle = tab.title;
-#ifndef XP_MACOSX
- docTitle += " - " + gBrandBundle.getString("brandFullName");
-#endif
+ if (!gPlatformOSX)
+ docTitle += " - " + gBrandBundle.getString("brandFullName");
document.title = docTitle;
}
}
]]>
</body>
</method>
+ <!--
+ - Updates the global state to reflect the active tab's thinking
+ - state (which the caller provides).
+ -->
+ <method name="_setActiveThinkingState">
+ <parameter name="aThinkingState"/>
+ <body><![CDATA[
+ if (aThinkingState) {
+ statusFeedback.showProgress(0);
+ if (typeof(aThinkingState) == "string")
+ statusFeedback.showStatusString(aThinkingState);
+ gStatusBar.setAttribute("mode","undetermined");
+ }
+ else {
+ statusFeedback.showProgress(0);
+ gStatusBar.setAttribute("mode", "normal");
+ }
+ ]]></body>
+ </method>
+ <method name="setTabThinking">
+ <parameter name="aTabNodeOrInfo"/>
+ <parameter name="aThinking"/>
+ <body>
+ <![CDATA[
+ let [iTab, tab, tabNode] = this._getTabContextForTabbyThing(
+ aTabNodeOrInfo, false);
+ let isSelected = (iTab == this.tabContainer.selectedIndex);
+
+ // if we are the current tab, update the cursor
+ if (isSelected)
+ this._setActiveThinkingState(aThinking);
+
+ // if we are busy, hint our tab
+ if (aThinking) {
+ tabNode.setAttribute("thinking", "true");
+ }
+ else {
+ // if we were thinking and are not selected, set the
+ // "wasThinking" attribute.
+ if (tab.thinking && !isSelected)
+ tabNode.setAttribute("wasThinking", "true");
+ tabNode.removeAttribute("thinking");
+ }
+
+ // update the tab info to store the busy state.
+ tab.thinking = aThinking;
+ ]]>
+ </body>
+ </method>
+ <method name="setTabBusy">
+ <parameter name="aTabNodeOrInfo"/>
+ <parameter name="aBusy"/>
+ <body>
+ <![CDATA[
+ let [iTab, tab, tabNode] = this._getTabContextForTabbyThing(
+ aTabNodeOrInfo, false);
+ let isSelected = (iTab == this.tabContainer.selectedIndex);
+
+ // if we are the current tab, update the cursor
+ if (isSelected)
+ SetBusyCursor(window, aBusy);
+
+ // if we are busy, hint our tab
+ if (aBusy) {
+ tabNode.setAttribute("busy", "true");
+ }
+ else {
+ // if we were busy and are not selected, set the
+ // "wasBusy" attribute.
+ if (tab.busy && !isSelected)
+ tabNode.setAttribute("wasBusy", "true");
+ tabNode.removeAttribute("busy");
+ }
+
+ // update the tab info to store the busy state.
+ tab.busy = aBusy;
+ ]]>
+ </body>
+ </method>
<method name="onTabContextMenuShowing">
<parameter name="aTabNode"/>
<body>
<![CDATA[
// To determine whether the 'Close Tab' context menu should be
// visible according to the tab node that was being clicked on.
// If the tab node is not closable, the context menu should not
// be shown.
--- a/mail/base/content/threadPane.js
+++ b/mail/base/content/threadPane.js
@@ -85,114 +85,16 @@ function ThreadPaneOnClick(event)
else if (col.value.id == "threadCol" && !event.shiftKey &&
(event.ctrlKey || event.metaKey)) {
gDBView.ExpandAndSelectThreadByIndex(row.value, true);
event.stopPropagation();
}
}
}
-function nsMsgDBViewCommandUpdater()
-{
- _selectionSummarized: false;
- _selectionTimeout: null;
-}
-
-nsMsgDBViewCommandUpdater.prototype =
-{
- updateCommandStatus : function()
- {
- // the back end is smart and is only telling us to update command status
- // when the # of items in the selection has actually changed.
- UpdateMailToolbar("dbview driven, thread pane");
- },
-
- displayMessageChanged : function(aFolder, aSubject, aKeywords)
- {
- if (!gDBView.suppressMsgDisplay)
- setTitleFromFolder(aFolder, aSubject);
- ClearPendingReadTimer(); // we are loading / selecting a new message so kill the mark as read timer for the currently viewed message
- gHaveLoadedMessage = true;
- goUpdateCommand("button_junk");
- },
-
- updateNextMessageAfterDelete : function()
- {
- SetNextMessageAfterDelete();
- },
-
- /**
- * This method either handles the selection, or sets a timer to handle
- * it once it stops changing.
- */
- showSummary: function(aThis, aSelCount)
- {
- aThis._selectionSummarized = true;
- let selectedMsgUris = GetSelectedMessages();
- let selCount = selectedMsgUris ? selectedMsgUris.length : 0;
- if (selCount < 2) {
- aThis.summarizeSelection();
- return;
- }
- if (selCount != aSelCount) {
- clearTimeout(aThis._selectionTimeout);
- aThis._selectionTimeout = setTimeout(aThis.showSummary, 100, aThis, selCount);
- return;
- }
-
- let firstThreadId = messenger.msgHdrFromURI(selectedMsgUris[0]).threadId;
- for (let i = 1; i < selectedMsgUris.length; ++i)
- {
- let msgHdr = messenger.msgHdrFromURI(selectedMsgUris[i]);
- if (msgHdr.threadId != firstThreadId) { // at least more than one thread
- summarizeMultipleSelection(selectedMsgUris);
- return;
- }
- }
- // must be just one thread.
- summarizeThread(selectedMsgUris);
- },
-
- summarizeSelection: function()
- {
- // First handle immediately the cases where we're not going to summarize.
- let selectedMsgUris = GetSelectedMessages();
- if (!selectedMsgUris || (selectedMsgUris.length == 1)) {
- pickMessagePane("singlemessage");
- this._selectionSummarized = false;
- return false;
- }
-
- if (! gPrefBranch.getBoolPref("mail.operate_on_msgs_in_collapsed_threads")) {
- ClearMessagePane();
- this._selectionSummarized = false;
- return false;
- }
-
- // If we are already summarized, let's make sure the selection count
- // isn't changing rapidly, by checking again in 100 msec.
- if (this._selectionSummarized) {
- clearTimeout(this._selectionTimeout);
- this._selectionTimeout = setTimeout(this.showSummary, 100, this, selectedMsgUris.length);
- return true;
- }
- this.showSummary(this, selectedMsgUris.length);
- return true;
- },
-
- QueryInterface : function(iid)
- {
- if (iid.equals(Components.interfaces.nsIMsgDBViewCommandUpdater) ||
- iid.equals(Components.interfaces.nsISupports))
- return this;
-
- throw Components.results.NS_NOINTERFACE;
- }
-}
-
function HandleColumnClick(columnID)
{
const columnMap = {dateCol: 'byDate',
receivedCol: 'byReceived',
senderCol: 'byAuthor',
recipientCol: 'byRecipient',
subjectCol: 'bySubject',
locationCol: 'byLocation',
@@ -213,270 +115,172 @@ function HandleColumnClick(columnID)
if (columnID in columnMap) {
sortType = columnMap[columnID];
} else {
// If the column isn't in the map, check and see if it's a custom column
try {
// try to grab the columnHandler (an error is thrown if it does not exist)
columnHandler = gDBView.getColumnHandler(columnID);
- // it exists - save this column ID in the customSortCol property of
- // dbFolderInfo for later use (see nsIMsgDBView.cpp)
- gDBView.db.dBFolderInfo.setProperty('customSortCol', columnID);
+ // it exists - set it to be the current custom column
+ gDBView.curCustomColumn = columnID;
sortType = "byCustom";
} catch(err) {
dump("unsupported sort column: " + columnID + " - no custom handler installed. (Error was: " + err + ")\n");
return; // bail out
}
}
- var dbview = GetDBView();
+ let viewWrapper = gFolderDisplay.view;
var simpleColumns = false;
try {
simpleColumns = !pref.getBoolPref("mailnews.thread_pane_column_unthreads");
}
catch (ex) {
}
if (sortType == "byThread") {
if (simpleColumns)
MsgToggleThreaded();
- else if (dbview.viewFlags & nsMsgViewFlagsType.kThreadedDisplay)
+ else if (viewWrapper.showThreaded)
MsgReverseSortThreadPane();
else
MsgSortByThread();
}
else {
- if (!simpleColumns && (dbview.viewFlags & nsMsgViewFlagsType.kThreadedDisplay)) {
- dbview.viewFlags &= ~nsMsgViewFlagsType.kThreadedDisplay;
+ if (!simpleColumns && viewWrapper.showThreaded) {
+ viewWrapper.showUnthreaded = true;
MsgSortThreadPane(sortType);
}
- else if (dbview.sortType == nsMsgViewSortType[sortType]) {
+ else if (viewWrapper.primarySortType == nsMsgViewSortType[sortType]) {
MsgReverseSortThreadPane();
}
else {
MsgSortThreadPane(sortType);
}
}
}
function ThreadPaneDoubleClick()
{
const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
if (IsSpecialFolderSelected(nsMsgFolderFlags.Drafts, true)) {
MsgComposeDraftMessage();
}
else if(IsSpecialFolderSelected(nsMsgFolderFlags.Templates, true)) {
- var loadedFolder = GetLoadedMsgFolder();
- var messageArray = GetSelectedMessages();
-
ComposeMessage(Components.interfaces.nsIMsgCompType.Template,
Components.interfaces.nsIMsgCompFormat.Default,
- loadedFolder, messageArray);
+ gFolderDisplay.displayedFolder,
+ gFolderDisplay.selectedMessageUris);
}
else {
MsgOpenSelectedMessages();
}
}
function ThreadPaneKeyPress(event)
{
if (event.keyCode == KeyEvent.DOM_VK_RETURN)
ThreadPaneDoubleClick();
}
function MsgSortByThread()
{
- var dbview = GetDBView();
- dbview.viewFlags |= nsMsgViewFlagsType.kThreadedDisplay;
- dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
+ gFolderDisplay.view.showThreaded = true;
MsgSortThreadPane('byDate');
}
function MsgSortThreadPane(sortName)
{
var sortType = nsMsgViewSortType[sortName];
- var dbview = GetDBView();
-
- // turn off grouping
- dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
-
- dbview.sort(sortType, nsMsgViewSortOrder.ascending);
- UpdateSortIndicators(sortType, nsMsgViewSortOrder.ascending);
+ // legacy behavior dictates we un-group-by-sort if we were. this probably
+ // deserves a UX call...
+ gFolderDisplay.view.showGroupedBySort = false;
+ gFolderDisplay.view.sort(sortType, nsMsgViewSortOrder.ascending)
}
function MsgReverseSortThreadPane()
{
- var dbview = GetDBView();
- if (dbview.sortOrder == nsMsgViewSortOrder.ascending) {
- MsgSortDescending();
- }
- else {
- MsgSortAscending();
- }
+ if (gFolderDisplay.view.isSortedAscending)
+ gFolderDisplay.view.sortDescending();
+ else
+ gFolderDisplay.view.sortAscending();
}
function MsgToggleThreaded()
{
- var dbview = GetDBView();
- var newViewFlags = dbview.viewFlags ^ nsMsgViewFlagsType.kThreadedDisplay;
- newViewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
- dbview.viewFlags = newViewFlags;
-
- dbview.sort(dbview.sortType, dbview.sortOrder);
- UpdateSortIndicators(dbview.sortType, dbview.sortOrder);
+ if (gFolderDisplay.view.showThreaded)
+ gFolderDisplay.view.showUnthreaded = true;
+ else
+ gFolderDisplay.view.showThreaded = true;
}
function MsgSortThreaded()
{
- var dbview = GetDBView();
- var viewFlags = dbview.viewFlags;
- let wasGrouped = viewFlags & nsMsgViewFlagsType.kGroupBySort;
- dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
- // if we were grouped, and not a saved search, just rebuild the view
- if (wasGrouped && !(gMsgFolderSelected.flags &
- Components.interfaces.nsMsgFolderFlags.Virtual))
- SwitchView("cmd_viewAllMsgs");
- // Toggle if not already threaded.
- else if ((viewFlags & nsMsgViewFlagsType.kThreadedDisplay) == 0)
- MsgToggleThreaded();
+ gFolderDisplay.view.showThreaded = true;
}
function MsgGroupBySort()
{
- var dbview = GetDBView();
- var viewFlags = dbview.viewFlags;
- var sortOrder = dbview.sortOrder;
- var sortType = dbview.sortType;
- var count = new Object;
- var msgFolder = dbview.msgFolder;
-
- var sortTypeSupportsGrouping = (sortType == nsMsgViewSortType.byAuthor
- || sortType == nsMsgViewSortType.byDate || sortType == nsMsgViewSortType.byReceived || sortType == nsMsgViewSortType.byPriority
- || sortType == nsMsgViewSortType.bySubject || sortType == nsMsgViewSortType.byTags
- || sortType == nsMsgViewSortType.byStatus || sortType == nsMsgViewSortType.byRecipient
- || sortType == nsMsgViewSortType.byAccount || sortType == nsMsgViewSortType.byFlagged
- || sortType == nsMsgViewSortType.byAttachments);
-
- if (!sortTypeSupportsGrouping)
- return; // we shouldn't be trying to group something we don't support grouping for...
-
- viewFlags |= nsMsgViewFlagsType.kThreadedDisplay | nsMsgViewFlagsType.kGroupBySort;
- if (gDBView &&
- gMsgFolderSelected.flags & Components.interfaces.nsMsgFolderFlags.Virtual)
- {
- gDBView.viewFlags = viewFlags;
- UpdateSortIndicators(sortType, nsMsgViewSortOrder.ascending);
- return;
- }
- // null this out, so we don't try sort.
- if (gDBView) {
- gDBView.close();
- gDBView = null;
- }
- gDBView = Components.classes["@mozilla.org/messenger/msgdbview;1?type=group"]
- .createInstance(Components.interfaces.nsIMsgDBView);
-
- if (!gThreadPaneCommandUpdater)
- gThreadPaneCommandUpdater = new nsMsgDBViewCommandUpdater();
-
-
- gDBView.init(messenger, msgWindow, gThreadPaneCommandUpdater);
- gDBView.open(msgFolder, sortType, sortOrder, viewFlags, count);
- RerootThreadPane();
- UpdateSortIndicators(sortType, nsMsgViewSortOrder.ascending);
- Components.classes["@mozilla.org/observer-service;1"]
- .getService(Components.interfaces.nsIObserverService)
- .notifyObservers(msgFolder, "MsgCreateDBView",
- Components.interfaces.nsMsgViewType.eShowAllThreads + ":" + viewFlags);
+ gFolderDisplay.view.showGroupedBySort = true;
}
function MsgSortUnthreaded()
{
- // Toggle if not already unthreaded.
- if ((GetDBView().viewFlags & nsMsgViewFlagsType.kThreadedDisplay) != 0)
- MsgToggleThreaded();
+ gFolderDisplay.view.showUnthreaded = true;
}
function MsgSortAscending()
{
- var dbview = GetDBView();
- dbview.sort(dbview.sortType, nsMsgViewSortOrder.ascending);
- UpdateSortIndicators(dbview.sortType, nsMsgViewSortOrder.ascending);
+ gFolderDisplay.view.sortAscending();
}
function MsgSortDescending()
{
- var dbview = GetDBView();
- dbview.sort(dbview.sortType, nsMsgViewSortOrder.descending);
- UpdateSortIndicators(dbview.sortType, nsMsgViewSortOrder.descending);
+ gFolderDisplay.view.sortDescending();
}
-function groupedBySortUsingDummyRow()
-{
- return (gDBView.viewFlags & nsMsgViewFlagsType.kGroupBySort) &&
- (gDBView.sortType != nsMsgViewSortType.bySubject);
-}
-
+// XXX this should probably migrate into FolderDisplayWidget, or whatever
+// FolderDisplayWidget ends up using if it refactors column management out.
function UpdateSortIndicators(sortType, sortOrder)
{
// Remove the sort indicator from all the columns
var treeColumns = document.getElementById('threadCols').childNodes;
for (var i = 0; i < treeColumns.length; i++)
- treeColumns[i].removeAttribute('sortDirection');
+ treeColumns[i].removeAttribute("sortDirection");
// show the twisties if the view is threaded
var threadCol = document.getElementById("threadCol");
+ var subjectCol = document.getElementById("subjectCol");
var sortedColumn;
// set the sort indicator on the column we are sorted by
var colID = ConvertSortTypeToColumnID(sortType);
if (colID)
sortedColumn = document.getElementById(colID);
- var dbview = GetDBView();
- var currCol = dbview.viewFlags & nsMsgViewFlagsType.kGroupBySort
- ? sortedColumn : document.getElementById("subjectCol");
-
- if (dbview.viewFlags & nsMsgViewFlagsType.kGroupBySort)
- {
- var threadTree = document.getElementById("threadTree");
- var subjectCol = document.getElementById("subjectCol");
+ var viewWrapper = gFolderDisplay.view;
- if (groupedBySortUsingDummyRow())
- {
- currCol.removeAttribute("primary");
- subjectCol.setAttribute("primary", "true");
- }
-
- // hide the threaded column when in grouped view since you can't do
- // threads inside of a group.
- document.getElementById("threadCol").collapsed = true;
- }
+ // the thread column is not visible when we are grouped by sort
+ document.getElementById("threadCol").collapsed = viewWrapper.showGroupedBySort;
- // clear primary attribute from group column if going to a non-grouped view.
- if (!(dbview.viewFlags & nsMsgViewFlagsType.kGroupBySort))
- document.getElementById("threadCol").collapsed = false;
+ // show twisties only when grouping or threading
+ if (viewWrapper.showGroupedBySort || viewWrapper.showThreaded)
+ subjectCol.setAttribute("primary", "true");
+ else
+ subjectCol.removeAttribute("primary");
- if ((dbview.viewFlags & nsMsgViewFlagsType.kThreadedDisplay) && !groupedBySortUsingDummyRow()) {
+ // If threading, set the sort direction on the thread column which causes it
+ // to be able to 'light up' or otherwise indicate threading is active.
+ if (viewWrapper.showThreaded)
threadCol.setAttribute("sortDirection", "ascending");
- currCol.setAttribute("primary", "true");
- }
- else {
- threadCol.removeAttribute("sortDirection");
- currCol.removeAttribute("primary");
- }
- if (sortedColumn) {
- if (sortOrder == nsMsgViewSortOrder.ascending) {
- sortedColumn.setAttribute("sortDirection","ascending");
- }
- else {
- sortedColumn.setAttribute("sortDirection","descending");
- }
- }
+ if (sortedColumn)
+ sortedColumn.setAttribute("sortDirection",
+ sortOrder == nsMsgViewSortOrder.ascending ?
+ "ascending" : "descending");
}
function IsSpecialFolderSelected(flags, checkAncestors)
{
var selectedFolder = GetThreadPaneFolder();
return IsSpecialFolder(selectedFolder, flags, checkAncestors);
}
@@ -490,37 +294,16 @@ function GetThreadPaneFolder()
try {
return gDBView.msgFolder;
}
catch (ex) {
return null;
}
}
-function EnsureRowInThreadTreeIsVisible(index)
-{
- if (index < 0)
- return;
-
- var tree = GetThreadTree();
- tree.treeBoxObject.ensureRowIsVisible(index);
-}
-
-function RerootThreadPane()
-{
- SetNewsFolderColumns();
-
- var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
- if (treeView)
- {
- var tree = GetThreadTree();
- tree.boxObject.QueryInterface(Components.interfaces.nsITreeBoxObject).view = treeView;
- }
-}
-
function ThreadPaneOnLoad()
{
var tree = GetThreadTree();
// We won't have the tree if we're in a message window, so exit silently
if (!tree)
return;
tree.addEventListener("click",ThreadPaneOnClick,true);
@@ -533,13 +316,12 @@ function ThreadPaneOnLoad()
tree.addEventListener("mousedown",TreeOnMouseDown,true);
let delay = pref.getIntPref("mailnews.threadpane_select_delay");
document.getElementById("threadTree")._selectDelay = delay;
}
function ThreadPaneSelectionChanged()
{
UpdateStatusMessageCounts(gMsgFolderSelected);
- if (!gRightMouseButtonDown)
- GetThreadTree().view.selectionChanged();
+ GetThreadTree().view.selectionChanged();
}
addEventListener("load",ThreadPaneOnLoad,true);
--- a/mail/base/content/widgetglue.js
+++ b/mail/base/content/widgetglue.js
@@ -44,16 +44,21 @@
* and then calls a function/command in commandglue
*/
//The eventual goal is for this file to go away and its contents to be brought into
//mailWindowOverlay.js. This is currently being done.
function MsgToggleMessagePane()
{
+ // Bail without doing anything if we are not a folder tab.
+ let currentTabInfo = document.getElementById("tabmail").currentTabInfo;
+ if (currentTabInfo.mode.name != "folder")
+ return;
+
var splitter = document.getElementById("threadpane-splitter");
var state = splitter.getAttribute("state");
if (state == "collapsed")
splitter.setAttribute("state", null);
else
splitter.setAttribute("state", "collapsed")
ChangeMessagePaneVisibility(IsMessagePaneCollapsed());
--- a/mail/base/jar.mn
+++ b/mail/base/jar.mn
@@ -9,37 +9,40 @@ messenger.jar:
% style chrome://global/content/customizeToolbar.xul chrome://messenger/skin/addressbook/addressbook.css
% style chrome://global/content/customizeToolbar.xul chrome://messenger/skin/messengercompose/messengercompose.css
% style chrome://global/content/customizeToolbar.xul chrome://messenger/skin/smime/msgCompSMIMEOverlay.css
% style chrome://global/content/customizeToolbar.xul chrome://messenger/skin/primaryToolbar.css
content/messenger/accountCreation.dtd (content/accountCreation.dtd)
content/messenger/accountCreation.properties (content/accountCreation.properties)
content/messenger/accountCreationModel.properties (content/accountCreationModel.properties)
content/messenger/accountCreationUtil.properties (content/accountCreationUtil.properties)
-* content/messenger/mailWindow.js (content/mailWindow.js)
-* content/messenger/mailWindowOverlay.js (content/mailWindowOverlay.js)
+ content/messenger/mailWindow.js (content/mailWindow.js)
+ content/messenger/messageDisplay.js (content/messageDisplay.js)
+ content/messenger/folderDisplay.js (content/folderDisplay.js)
+ content/messenger/mailWindowOverlay.js (content/mailWindowOverlay.js)
* content/messenger/mailWindowOverlay.xul (content/mailWindowOverlay.xul)
-* content/messenger/extraCustomizeItems.xul (content/extraCustomizeItems.xul)
+ content/messenger/extraCustomizeItems.xul (content/extraCustomizeItems.xul)
* content/messenger/mailOverlay.xul (content/mailOverlay.xul)
* content/messenger/messageWindow.xul (content/messageWindow.xul)
-* content/messenger/messageWindow.js (content/messageWindow.js)
-* content/messenger/mailContextMenus.js (content/mailContextMenus.js)
+ content/messenger/messageWindow.js (content/messageWindow.js)
+ content/messenger/mailContextMenus.js (content/mailContextMenus.js)
* content/messenger/messenger.xul (content/messenger.xul)
* content/messenger/hiddenWindow.xul (content/hiddenWindow.xul)
* content/messenger/hiddenWindow.js (content/hiddenWindow.js)
-* content/messenger/msgHdrViewOverlay.js (content/msgHdrViewOverlay.js)
+ content/messenger/msgHdrViewOverlay.js (content/msgHdrViewOverlay.js)
content/messenger/msgHdrViewOverlay.xul (content/msgHdrViewOverlay.xul)
+ content/messenger/msgViewNavigation.js (content/msgViewNavigation.js)
content/messenger/mailWidgets.xml (content/mailWidgets.xml)
content/messenger/editContactOverlay.js (content/editContactOverlay.js)
* content/messenger/editContactOverlay.xul (content/editContactOverlay.xul)
-* content/messenger/msgMail3PaneWindow.js (content/msgMail3PaneWindow.js)
-* content/messenger/mail3PaneWindowCommands.js (content/mail3PaneWindowCommands.js)
+ content/messenger/msgMail3PaneWindow.js (content/msgMail3PaneWindow.js)
+ content/messenger/mail3PaneWindowCommands.js (content/mail3PaneWindowCommands.js)
* content/messenger/mailCommands.js (content/mailCommands.js)
* content/messenger/mailCore.js (content/mailCore.js)
-* content/messenger/commandglue.js (content/commandglue.js)
+ content/messenger/commandglue.js (content/commandglue.js)
* content/messenger/widgetglue.js (content/widgetglue.js)
* content/messenger/SearchDialog.xul (content/SearchDialog.xul)
content/messenger/SearchDialog.js (content/SearchDialog.js)
* content/messenger/ABSearchDialog.xul (content/ABSearchDialog.xul)
* content/messenger/ABSearchDialog.js (content/ABSearchDialog.js)
* content/messenger/FilterListDialog.xul (content/FilterListDialog.xul)
* content/messenger/FilterListDialog.js (content/FilterListDialog.js)
content/messenger/specialTabs.js (content/specialTabs.js)
@@ -48,25 +51,25 @@ messenger.jar:
* content/messenger/aboutDialog.xul (content/aboutDialog.xul)
* content/messenger/aboutDialog.js (content/aboutDialog.js)
* content/messenger/aboutRights.xhtml (content/aboutRights.xhtml)
* content/messenger/systemIntegrationDialog.xul (content/systemIntegrationDialog.xul)
* content/messenger/systemIntegrationDialog.js (content/systemIntegrationDialog.js)
content/messenger/folderPane.js (content/folderPane.js)
* content/messenger/msgSelectOffline.xul (content/msgSelectOffline.xul)
* content/messenger/msgPrintEngine.xul (content/msgPrintEngine.xul)
-* content/messenger/searchBar.js (content/searchBar.js)
-* content/messenger/phishingDetector.js (content/phishingDetector.js)
+ content/messenger/searchBar.js (content/searchBar.js)
+ content/messenger/phishingDetector.js (content/phishingDetector.js)
* content/messenger/mail-offline.js (content/mail-offline.js)
content/messenger/about-footer.png (content/about-footer.png)
content/messenger/aboutDialog.css (content/aboutDialog.css)
* content/messenger/credits.xhtml (content/credits.xhtml)
content/messenger/messenger.css (content/messenger.css)
* content/messenger/search.xml (content/search.xml)
-* content/messenger/tabmail.xml (content/tabmail.xml)
+ content/messenger/tabmail.xml (content/tabmail.xml)
* content/messenger/newmailalert.xul (content/newmailalert.xul)
content/messenger/newmailalert.js (content/newmailalert.js)
content/messenger/newTagDialog.xul (content/newTagDialog.xul)
content/messenger/newTagDialog.js (content/newTagDialog.js)
* content/messenger/viewSourceOverlay.xul (content/viewSourceOverlay.xul)
* content/messenger/configEditorOverlay.xul (content/configEditorOverlay.xul)
content/messenger/composerOverlay.css (content/composerOverlay.css)
content/messenger/threadPane.js (content/threadPane.js)
--- a/mail/components/activity/content/activity.js
+++ b/mail/components/activity/content/activity.js
@@ -46,286 +46,314 @@
////////////////////////////////////////////////////////////////////////////////
//// Globals
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/PluralForm.jsm");
Components.utils.import("resource://app/modules/gloda/log4moz.js");
-let gActivityMgrListener = null;
-let gActivitiesView = null;
-let gActivityLogger = Log4Moz.getConfiguredLogger("activitymgr");
-
const nsActProcess = Components.Constructor("@mozilla.org/activity-process;1",
"nsIActivityProcess", "init");
const nsActEvent = Components.Constructor("@mozilla.org/activity-event;1",
"nsIActivityEvent", "init");
const nsActWarning = Components.Constructor("@mozilla.org/activity-warning;1",
"nsIActivityWarning", "init");
-////////////////////////////////////////////////////////////////////////////////
-//// An object to monitor nsActivityManager operations. This class acts as
-//// binding layer between nsActivityManager and nsActivityManagerUI objects.
-
-function ActivityMgrListener()
-{}
-
-ActivityMgrListener.prototype = {
-
- onAddedActivity: function(aID, aActivity) {
- gActivityLogger.info("added activity: " + aID + " " + aActivity)
- addActivityBinding(aID, aActivity);
- },
-
- onRemovedActivity: function(aID) {
- removeActivityBinding(aID);
- }
-};
-
-////////////////////////////////////////////////////////////////////////////////
-//// Utility Functions for Activity binding management
-
-/**
- * Creates the proper binding for the given activity
- */
-function createActivityBinding(aActivity)
-{
- let bindingName = aActivity.bindingName;
- let binding = document.createElement(bindingName);
-
- if (binding)
- binding.setAttribute('actID', aActivity.id);
-
- return binding;
-}
-
-/**
- * Returns the activity group binding that matches the context_type
- * and context of the given activity, if any.
- */
-function getActivityGroupBindingByContext(aContextType, aContextObj)
+var activityObject =
{
- for (let i = 0; i < gActivitiesView.itemCount; i++) {
- let item = gActivitiesView.getItemAtIndex(i)
- if (item.isGroup &&
- item.contextType == aContextType &&
- item.contextObj == aContextObj) {
- return item;
- }
- }
- return null;
-}
+
+ _activityMgrListener: null,
+ _activitiesView: null,
+ _activityLogger: Log4Moz.getConfiguredLogger("activitymgr"),
+ _ignoreNotifications: false,
+
+ selectAll: function() {
+ this._activitiesView.selectAll();
+ },
-/**
- * Inserts the given binding into the correct position on the
- * activity manager window.
- */
-function placeActivityBinding(aBinding)
-{
- if (aBinding.isGroup || aBinding.isProcess)
- gActivitiesView.insertBefore(aBinding, gActivitiesView.firstChild);
- else {
- let next = gActivitiesView.firstChild;
- while (next && (next.isWarning || next.isProcess || next.isGroup))
- next = next.nextSibling;
- if (next)
- gActivitiesView.insertBefore(aBinding, next);
- else
- gActivitiesView.appendChild(aBinding);
- }
-}
+ //////////////////////////////////////////////////////////////////////////////
+ //// An object to monitor nsActivityManager operations. This class acts as
+ //// binding layer between nsActivityManager and nsActivityManagerUI objects.
+
+ /**
+ * Note: The prototype for this function is set at the bottom of this file.
+ */
+ ActivityMgrListener: function() {},
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Utility Functions for Activity binding management
-/**
- * Adds a new binding to activity manager window for the
- * given activity. It is called by ActivityMgrListener when
- * a new activity is added into the activity manager's internal
- * list.
- */
-function addActivityBinding(aID, aActivity)
-{
- try {
- gActivityLogger.info("Adding ActivityBinding: " + aID + ", " + aActivity)
- // get |groupingStyle| of the activity. Grouping style determines whether we
- // show the activity standalone or grouped by context in the activity manager
- // window.
- let isGroupByContext = (aActivity.groupingStyle ==
- Components.interfaces.nsIActivity.GROUPING_STYLE_BYCONTEXT);
-
- // find out if an activity group has already been created for this context
- let group = null;
- if (isGroupByContext) {
- group = getActivityGroupBindingByContext(aActivity.contextType,
- aActivity.contextObj);
- // create a group if it's not already created.
- if (!group) {
- group = document.createElement("activity-group");
- gActivityLogger.info("created group element")
- // Set the context type and object of the newly created group
- group.contextType = aActivity.contextType;
- group.contextObj = aActivity.contextObj;
- group.contextDisplayText = aActivity.contextDisplayText;
-
- // add group into the list
- placeActivityBinding(group);
+ /**
+ * Creates the proper binding for the given activity
+ */
+ createActivityBinding: function(aActivity) {
+ let bindingName = aActivity.bindingName;
+ let binding = document.createElement(bindingName);
+
+ if (binding)
+ binding.setAttribute('actID', aActivity.id);
+
+ return binding;
+ },
+
+ /**
+ * Returns the activity group binding that matches the context_type
+ * and context of the given activity, if any.
+ */
+ getActivityGroupBindingByContext: function(aContextType, aContextObj) {
+ for (let i = 0; i < this._activitiesView.itemCount; i++) {
+ let item = this._activitiesView.getItemAtIndex(i)
+ if (item.isGroup &&
+ item.contextType == aContextType &&
+ item.contextObj == aContextObj) {
+ return item;
}
}
-
- // create the appropriate binding for the activity
- let actBinding = createActivityBinding(aActivity);
- gActivityLogger.info("created activity binding")
-
- if (group) {
- // get the inner list element of the group
- let groupView = document.getAnonymousElementByAttribute(group, "anonid",
- "activityGroupView");
- groupView.appendChild(actBinding);
+ return null;
+ },
+
+ /**
+ * Inserts the given binding into the correct position on the
+ * activity manager window.
+ */
+ placeActivityBinding: function(aBinding) {
+ if (aBinding.isGroup || aBinding.isProcess)
+ this._activitiesView.insertBefore(aBinding,
+ this._activitiesView.firstChild);
+ else {
+ let next = this._activitiesView.firstChild;
+ while (next && (next.isWarning || next.isProcess || next.isGroup))
+ next = next.nextSibling;
+ if (next)
+ this._activitiesView.insertBefore(aBinding, next);
+ else
+ this._activitiesView.appendChild(aBinding);
}
- else {
- placeActivityBinding(actBinding);
- }
- } catch (e) {
- log.error("addActivityBinding: " + e);
- throw(e);
- }
-}
+ },
+
+ /**
+ * Adds a new binding to activity manager window for the
+ * given activity. It is called by ActivityMgrListener when
+ * a new activity is added into the activity manager's internal
+ * list.
+ */
+ addActivityBinding: function(aID, aActivity) {
+ try {
+ this._activityLogger.info("Adding ActivityBinding: " + aID + ", " +
+ aActivity)
+ // get |groupingStyle| of the activity. Grouping style determines
+ // whether we show the activity standalone or grouped by context in
+ // the activity manager window.
+ let isGroupByContext = (aActivity.groupingStyle ==
+ Components.interfaces.nsIActivity
+ .GROUPING_STYLE_BYCONTEXT);
+
+ // find out if an activity group has already been created for this context
+ let group = null;
+ if (isGroupByContext) {
+ group = this.getActivityGroupBindingByContext(aActivity.contextType,
+ aActivity.contextObj);
+ // create a group if it's not already created.
+ if (!group) {
+ group = document.createElement("activity-group");
+ this._activityLogger.info("created group element")
+ // Set the context type and object of the newly created group
+ group.contextType = aActivity.contextType;
+ group.contextObj = aActivity.contextObj;
+ group.contextDisplayText = aActivity.contextDisplayText;
+
+ // add group into the list
+ this.placeActivityBinding(group);
+ }
+ }
-/**
- * Removes the activity binding from the activity manager window.
- * It is called by ActivityMgrListener when the activity in question
- * is removed from the activity manager's internal list.
- */
-function removeActivityBinding(aID)
-{
- // Note: document.getAnonymousNodes(gActivitiesView); didn't work
- gActivityLogger.info("removing Activity ID: " + aID);
- for (let i = 0; i < gActivitiesView.itemCount; i++) {
- let item = gActivitiesView.getItemAtIndex(i);
- if (!item) {
- gActivityLogger.debug("returning as empty")
- return;
+ // create the appropriate binding for the activity
+ let actBinding = this.createActivityBinding(aActivity);
+ this._activityLogger.info("created activity binding")
+
+ if (group) {
+ // get the inner list element of the group
+ let groupView = document.getAnonymousElementByAttribute(group, "anonid",
+ "activityGroupView");
+ groupView.appendChild(actBinding);
+ }
+ else {
+ this.placeActivityBinding(actBinding);
+ }
+ } catch (e) {
+ this._activityLogger.error("addActivityBinding: " + e);
+ throw(e);
}
+ },
- if (!item.isGroup) {
- gActivityLogger.debug("is not a group, ")
- if (item.getAttribute('actID') == aID) {
- // since XBL dtors are not working properly when we remove the element,
- // we have to explicitly remove the binding from activities' listeners
- // list. See bug 230086 for details.
- item.detachFromActivity();
- gActivitiesView.removeChild(item);
- break;
+ /**
+ * Removes the activity binding from the activity manager window.
+ * It is called by ActivityMgrListener when the activity in question
+ * is removed from the activity manager's internal list.
+ */
+ removeActivityBinding: function(aID) {
+ // Note: document.getAnonymousNodes(_activitiesView); didn't work
+ this._activityLogger.info("removing Activity ID: " + aID);
+ for (let i = 0; i < this._activitiesView.itemCount; i++) {
+ let item = this._activitiesView.getItemAtIndex(i);
+ if (!item) {
+ this._activityLogger.debug("returning as empty")
+ return;
+ }
+
+ if (!item.isGroup) {
+ this._activityLogger.debug("is not a group, ")
+ if (item.getAttribute('actID') == aID) {
+ // since XBL dtors are not working properly when we remove the
+ // element, we have to explicitly remove the binding from
+ // activities' listeners list. See bug 230086 for details.
+ item.detachFromActivity();
+ this._activitiesView.removeChild(item);
+ break;
+ }
+ }
+ else {
+ let actbinding = document.getAnonymousElementByAttribute(item, 'actID',
+ aID);
+ if (actbinding) {
+ let groupView = document.getAnonymousElementByAttribute(item,
+ "anonid", "activityGroupView");
+ // since XBL dtors are not working properly when we remove the
+ // element, we have to explicitly remove the binding from
+ // activities' listeners list. See bug 230086 for details.
+ actbinding.detachFromActivity();
+ groupView.removeChild(actbinding);
+
+ // if the group becomes empty after the removal,
+ // get rid of the group as well
+ if (groupView.getRowCount() == 0)
+ this._activitiesView.removeChild(item);
+
+ break;
+ }
}
}
- else {
- let actbinding = document.getAnonymousElementByAttribute(item, 'actID',
- aID);
- if (actbinding) {
- let groupView = document.getAnonymousElementByAttribute(item, "anonid",
- "activityGroupView");
- // since XBL dtors are not working properly when we remove the element,
- // we have to explicitly remove the binding from activities' listeners
- // list. See bug 230086 for details.
- actbinding.detachFromActivity();
- groupView.removeChild(actbinding);
-
- // if the group becomes empty after the removal,
- // get rid of the group as well
- if (groupView.getRowCount() == 0)
- gActivitiesView.removeChild(item);
-
- break;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Startup, Shutdown
+
+ startup: function() {
+ try {
+ this._activitiesView = document.getElementById("activityView");
+
+ let activityManager = Components
+ .classes["@mozilla.org/activity-manager;1"]
+ .getService(Components.interfaces.nsIActivityManager);
+ let activities = activityManager.getActivities({});
+ for each (let [, activity] in Iterator(activities)) {
+ this.addActivityBinding(activity.id, activity);
}
- }
- }
-}
+
+ // start listening changes in the activity manager's
+ // internal list
+ this._activityMgrListener = new this.ActivityMgrListener();
+ activityManager.addListener(this._activityMgrListener);
-////////////////////////////////////////////////////////////////////////////////
-//// Startup, Shutdown
+ } catch (e) {
+ this._activityLogger.error("Exception: " + e )
+ }
+ },
-function Startup()
-{
- try {
- gActivitiesView = document.getElementById("activityView");
-
+ rebuild: function() {
let activityManager = Components.classes["@mozilla.org/activity-manager;1"]
.getService(Components.interfaces.nsIActivityManager);
let activities = activityManager.getActivities({});
- for each (let [, activity] in Iterator(activities)) {
- addActivityBinding(activity.id, activity);
+ for each (let [, activity] in Iterator(activities))
+ this.addActivityBinding(activity.id, activity);
+ },
+
+ shutdown: function() {
+ let activityManager = Components.classes["@mozilla.org/activity-manager;1"]
+ .getService(Components.interfaces.nsIActivityManager);
+ activityManager.removeListener(this._activityMgrListener);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Utility Functions
+
+ /**
+ * Remove all activities not in-progress from the activity list.
+ */
+ clearActivityList: function() {
+ this._activityLogger.debug("clearActivityList");
+
+ this._ignoreNotifications = true;
+ // If/when we implement search, we'll want to remove just the items
+ // that are on the search display, however for now, we'll just clear up
+ // everything.
+ Components.classes["@mozilla.org/activity-manager;1"]
+ .getService(Components.interfaces.nsIActivityManager)
+ .cleanUp();
+
+ // since XBL dtors are not working properly when we remove the element,
+ // we have to explicitly remove the binding from activities' listeners
+ // list. See bug 230086 for details.
+ for (let i = 0; i < this._activitiesView.itemCount; i++) {
+ let item = this._activitiesView.getItemAtIndex(i);
+ if (!item.isGroup)
+ item.detachFromActivity();
+ else {
+ let actbinding = document.getAnonymousElementByAttribute(item,
+ 'actID', '*');
+ while (actbinding) {
+ actbinding.detachFromActivity();
+ actbinding.parentNode.removeChild(actbinding);
+ actbinding = document.getAnonymousElementByAttribute(item,
+ 'actID', '*');
+ }
+ }
}
- // start listening changes in the activity manager's
- // internal list
- gActivityMgrListener = new ActivityMgrListener();
- activityManager.addListener(gActivityMgrListener);
-
- } catch (e) {
- gActivityLogger.error("Exception: " + e )
- }
-}
-
-function Shutdown()
-{
- let activityManager = Components.classes["@mozilla.org/activity-manager;1"]
- .getService(Components.interfaces.nsIActivityManager);
- activityManager.removeListener(gActivityMgrListener);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//// Utility Functions
-
-/**
- * Helper function to replace a placeholder string with a real string
- *
- * @param aText
- * Source text containing placeholder (e.g., #1)
- * @param aIndex
- * Index number of placeholder to replace
- * @param aValue
- * New string to put in place of placeholder
- * @return The string with placeholder replaced with the new string
- */
-function replaceInsert(aText, aIndex, aValue)
-{
- return aText.replace("#" + aIndex, aValue);
-}
+ let (empty = this._activitiesView.cloneNode(false)) {
+ this._activitiesView.parentNode.replaceChild(empty, this._activitiesView);
+ this._activitiesView = empty;
+ }
+ this.rebuild();
+ this._ignoreNotifications = false;
+ this._activitiesView.focus();
+ },
-/**
- * Remove all activities not in-progress from the activity list.
- */
-function clearActivityList()
-{
- gActivityLogger.debug("clearActivityList");
-
- // If/when we implement search, we'll want to remove just the items that
- // are on the search display, however for now, we'll just clear up everything.
- Components.classes["@mozilla.org/activity-manager;1"]
- .getService(Components.interfaces.nsIActivityManager)
- .cleanUp();
-
- gActivitiesView.focus();
-}
+ processKeyEvent: function(event) {
+ switch (event.keyCode) {
+ case event.DOM_VK_RIGHT:
+ if (event.target.tagName == 'richlistbox') {
+ let richlistbox = event.target.selectedItem.processes;
+ if (richlistbox.tagName == 'xul:richlistbox') {
+ richlistbox.focus();
+ richlistbox.selectItem(richlistbox.getItemAtIndex(0));
+ }
+ }
+ break;
+ case event.DOM_VK_LEFT:
+ if (event.target.tagName == 'activity-group') {
+ var parent = event.target.parentNode;
+ if (parent.tagName == 'richlistbox') {
+ event.target.processes.clearSelection();
+ parent.selectItem(event.target);
+ parent.focus();
+ }
+ }
+ break;
+ }
+ },
+};
-function processKeyEvent(event)
-{
- switch (event.keyCode) {
- case event.DOM_VK_RIGHT:
- if (event.target.tagName == 'richlistbox') {
- let richlistbox = event.target.selectedItem.processes;
- if (richlistbox.tagName == 'xul:richlistbox') {
- richlistbox.focus();
- richlistbox.selectItem(richlistbox.getItemAtIndex(0));
- }
- }
- break;
- case event.DOM_VK_LEFT:
- if (event.target.tagName == 'activity-group') {
- var parent = event.target.parentNode;
- if (parent.tagName == 'richlistbox') {
- event.target.processes.clearSelection();
- parent.selectItem(event.target);
- parent.focus();
- }
- }
- break;
+activityObject.ActivityMgrListener.prototype = {
+
+ onAddedActivity: function(aID, aActivity) {
+ activityObject._activityLogger.info("added activity: " + aID + " " +
+ aActivity)
+ if (!activityObject._ignoreNotifications)
+ activityObject.addActivityBinding(aID, aActivity);
+ },
+
+ onRemovedActivity: function(aID) {
+ if (!activityObject._ignoreNotifications)
+ activityObject.removeActivityBinding(aID);
}
-}
+};
--- a/mail/components/activity/content/activity.xul
+++ b/mail/components/activity/content/activity.xul
@@ -63,17 +63,18 @@
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="activityManager" windowtype="Activity:Manager"
orient="vertical" title="&activity.title;"
statictitle="&activity.title;"
width="&window.width2;" height="&window.height;"
screenX="10" screenY="10"
persist="width height screenX screenY sizemode"
- onload="Startup();" onunload="Shutdown();"
+ onload="activityObject.startup();"
+ onunload="activityObject.shutdown();"
onclose="return closeWindow(false);">
<script type="application/javascript"
src="chrome://messenger/content/activity.js"/>
<script type="application/javascript"
src="chrome://global/content/contentAreaUtils.js"/>
<script type="application/javascript"
src="chrome://global/content/nsDragAndDrop.js"/>
@@ -86,18 +87,18 @@
<stringbundle id="activityStrings"
src="chrome://messenger/locale/activity.properties"/>
</stringbundleset>
<!-- Use this commandset for command which do not depend on focus or
selection -->
<commandset id="generalCommands">
<command id="cmd_selectAllActivities"
- oncommand="gActivitiesView.selectAll();"/>
- <command id="cmd_clearList" oncommand="clearActivityList();"/>
+ oncommand="activityObject.selectAll();"/>
+ <command id="cmd_clearList" oncommand="activityObject.clearActivityList();"/>
</commandset>
<keyset id="activityKeys">
<key id="key_removeFromList" keycode="VK_DELETE"
oncommand="performCommand('cmd_removeFromList');"/>
#ifdef XP_MACOSX
<key id="key_removeFromList2" keycode="VK_BACK"
oncommand="performCommand('cmd_removeFromList');"/>
@@ -120,17 +121,17 @@
<vbox id="contextMenuPalette" hidden="true">
<menuitem id="menuitem_selectAll" label="&selectAllCmd.label;"
accesskey="&selectAllCmd.accesskey;"
command="cmd_selectAllActivities"/>
</vbox>
<richlistbox id="activityView" class="activityview" seltype="multiple"
- flex="1" onkeypress="processKeyEvent(event)">
+ flex="1" onkeypress="activityObject.processKeyEvent(event)">
</richlistbox>
<windowdragbox id="search" align="center">
<button id="clearListButton" command="cmd_clearList"
label="&cmd.clearList.label;"
accesskey="&cmd.clearList.accesskey;"
tooltiptext="&cmd.clearList.tooltip;"/>
<spacer flex="1"/>
--- a/mail/components/activity/modules/sendLater.js
+++ b/mail/components/activity/modules/sendLater.js
@@ -220,16 +220,17 @@ let sendLaterModule =
this._sendProcess.setProgress(this._sendProcess.lastStatusText,
aMessageSendPercent, 100);
}
else if (aMessageSendPercent == 100) {
if (aMessageCopyPercent == 0) {
// Set send state to completed
if (this._sendProcess.state != Ci.nsIActivityProcess.STATE_COMPLETED)
this._sendProcess.state = Ci.nsIActivityProcess.STATE_COMPLETED;
+ this._replaceProcessWithEvent(this._sendProcess);
// Set copy state to in progress.
if (this._copyProcess.state != Ci.nsIActivityProcess.STATE_INPROGRESS)
this._copyProcess.state = Ci.nsIActivityProcess.STATE_INPROGRESS;
// We don't know the progress of the copy, so just set to 0, and we'll
// display an undetermined progress meter.
this._copyProcess.setProgress(this._copyProcess.lastStatusText,
@@ -238,17 +239,16 @@ let sendLaterModule =
else if (aMessageCopyPercent < 100) {
}
else {
// We need to set this to completed otherwise activity manager
// complains.
if (this._copyProcess.state != Ci.nsIActivityProcess.STATE_COMPLETED)
this._copyProcess.state = Ci.nsIActivityProcess.STATE_COMPLETED;
- this._replaceProcessWithEvent(this._sendProcess);
// Just drop the copy process, we don't need it now.
this.activityMgr.removeActivity(this._copyProcess.id);
this._sendProcess = null;
this._copyProcess = null;
}
}
},
--- a/mail/components/activity/nsActivityManager.js
+++ b/mail/components/activity/nsActivityManager.js
@@ -151,21 +151,28 @@ nsActivityManager.prototype = {
}
}
},
cleanUp: function () {
// Get the list of aIDs.
this.log.info("cleanUp\n");
for (var id in this._activities) {
- let state = this._activities[id].state;
- if (state != Ci.nsIActivityProcess.STATE_INPROGRESS ||
- state != Ci.nsIActivityProcess.STATE_PAUSED ||
- state != Ci.nsIActivityProcess.STATE_WAITINGFORINPUT ||
- state != Ci.nsIActivityProcess.STATE_WAITINGFORRETRY)
+ let activity = this._activities[id];
+ if (activity instanceof Ci.nsIActivityProcess) {
+ // Note: The .state property will return undefined if you aren't in
+ // this if-instanceof block.
+ let state = activity.state;
+ if (state != Ci.nsIActivityProcess.STATE_INPROGRESS &&
+ state != Ci.nsIActivityProcess.STATE_PAUSED &&
+ state != Ci.nsIActivityProcess.STATE_WAITINGFORINPUT &&
+ state != Ci.nsIActivityProcess.STATE_WAITINGFORRETRY)
+ this.removeActivity(id);
+ }
+ else
this.removeActivity(id);
}
},
getActivity: function(aID) {
if (!this._activities[aID])
throw Cr.NS_ERROR_NOT_AVAILABLE;
return this._activities[aID];
--- a/mail/components/compose/content/MsgComposeCommands.js
+++ b/mail/components/compose/content/MsgComposeCommands.js
@@ -1,48 +1,48 @@
-# -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is Mozilla Communicator client code, released
-# March 31, 1998.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 1998-1999
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-# David Bienvenu <bienvenu@nventure.com>
-# Olivier Parniere BT Global Services / Etat francais Ministere de la Defense
-# Simon Wilkinson <simon@sxw.org.uk>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * David Bienvenu <bienvenu@nventure.com>
+ * Olivier Parniere BT Global Services / Etat francais Ministere de la Defense
+ * Simon Wilkinson <simon@sxw.org.uk>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
// Ensure the activity modules are loaded for this window.
Components.utils.import("resource://app/modules/activity/activityModules.js");
/**
* interfaces
*/
const nsIMsgCompDeliverMode = Components.interfaces.nsIMsgCompDeliverMode;
@@ -52,17 +52,17 @@ const nsIMsgCompType = Components.interf
const nsIMsgCompFormat = Components.interfaces.nsIMsgCompFormat;
const nsIAbPreferMailFormat = Components.interfaces.nsIAbPreferMailFormat;
const nsIPlaintextEditorMail = Components.interfaces.nsIPlaintextEditor;
const nsISupportsString = Components.interfaces.nsISupportsString;
const mozISpellCheckingEngine = Components.interfaces.mozISpellCheckingEngine;
var sDictCount = 0;
-/* Create message window object. This is use by mail-offline.js and therefore should not be renamed. We need to avoid doing
+/* Create message window object. This is use by mail-offline.js and therefore should not be renamed. We need to avoid doing
this kind of cross file global stuff in the future and instead pass this object as parameter when needed by function
in the other js file.
*/
var msgWindow = Components.classes["@mozilla.org/messenger/msgwindow;1"]
.createInstance(Components.interfaces.nsIMsgWindow);
/**
* Global variables, need to be re-initialized every time mostly because we need to release them when the window close
@@ -111,17 +111,17 @@ var gEditingDraft;
const kComposeAttachDirPrefName = "mail.compose.attach.dir";
function InitializeGlobalVariables()
{
gAccountManager = Components.classes["@mozilla.org/messenger/account-manager;1"].getService(Components.interfaces.nsIMsgAccountManager);
gIOService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
gPromptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
-
+
gMsgCompose = null;
gWindowLocked = false;
gContentChanged = false;
gCurrentIdentity = null;
defaultSaveOperation = "draft";
gSendOrSaveOperationInProgress = false;
gAutoSaving = false;
gCloseWindowAfterSave = false;
@@ -178,34 +178,34 @@ function enableEditableFields()
var gComposeRecyclingListener = {
onClose: function() {
//Reset recipients and attachments
ReleaseAutoCompleteState();
awResetAllRows();
RemoveAllAttachments();
- // We need to clear the identity popup menu in case the user will change them.
+ // We need to clear the identity popup menu in case the user will change them.
// It will be rebuilt later in ComposeStartup
ClearIdentityListPopup(document.getElementById("msgIdentityPopup"));
-
+
//Clear the subject
GetMsgSubjectElement().value = "";
// be sure to clear the transaction manager for the subject
GetMsgSubjectElement().editor.transactionManager.clear();
SetComposeWindowTitle();
SetContentAndBodyAsUnmodified();
disableEditableFields();
ReleaseGlobalVariables();
// Clear the focus
awGetInputElement(1).removeAttribute('focused');
- //Reset Boxes size
+ //Reset Boxes size
document.getElementById("headers-box").removeAttribute("height");
document.getElementById("appcontent").removeAttribute("height");
document.getElementById("addresses-box").removeAttribute("width");
document.getElementById("attachments-box").removeAttribute("width");
//Reset menu options
document.getElementById("format_auto").setAttribute("checked", "true");
document.getElementById("priority_normal").setAttribute("checked", "true");
@@ -219,17 +219,17 @@ var gComposeRecyclingListener = {
if (showFormat.getAttribute("checked") == "true")
document.getElementById("FormatToolbar").hidden = false;
}
// Stop InlineSpellCheckerUI so personal dictionary is saved
enableInlineSpellCheck(false);
// clear any suggestions in the context menu
InlineSpellCheckerUI.clearSuggestionsFromMenu();
-
+
//Reset editor
EditorResetFontAndColorAttributes();
EditorCleanup();
//Release the nsIMsgComposeParams object
if (window.arguments && window.arguments[0])
window.arguments[0] = null;
@@ -265,31 +265,31 @@ var stateListener = {
gWindowLocked = false;
enableEditableFields();
updateComposeItems();
if (aResult== Components.results.NS_OK)
{
if (!gAutoSaving)
SetContentAndBodyAsUnmodified();
-
+
if (gCloseWindowAfterSave)
{
// Notify the SendListener that Send has been aborted and Stopped
if (gMsgCompose)
gMsgCompose.onSendNotPerformed(null, Components.results.NS_ERROR_ABORT);
MsgComposeCloseWindow(true);
}
}
// else if we failed to save, and we're autosaving, need to re-mark the editor
// as changed, so that we won't lose the changes.
else if (gAutoSaving)
{
- gMsgCompose.bodyModified = true;
+ gMsgCompose.bodyModified = true;
gContentChanged = true;
}
gAutoSaving = false;
gCloseWindowAfterSave = false;
},
SaveInFolderDone: function(folderURI) {
DisplaySaveFolderDlg(folderURI);
@@ -299,42 +299,42 @@ var stateListener = {
// all progress notifications are done through the nsIWebProgressListener implementation...
var progressListener = {
onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus)
{
if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_START)
{
document.getElementById('compose-progressmeter').setAttribute( "mode", "undetermined" );
}
-
+
if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
{
gSendOrSaveOperationInProgress = false;
document.getElementById('compose-progressmeter').setAttribute( "mode", "normal" );
document.getElementById('compose-progressmeter').setAttribute( "value", 0 );
document.getElementById('statusText').setAttribute('label', '');
}
},
-
+
onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress)
{
// Calculate percentage.
var percent;
- if ( aMaxTotalProgress > 0 )
+ if ( aMaxTotalProgress > 0 )
{
percent = Math.round( (aCurTotalProgress*100)/aMaxTotalProgress );
if ( percent > 100 )
percent = 100;
-
+
document.getElementById('compose-progressmeter').removeAttribute("mode");
-
+
// Advance progress meter.
document.getElementById('compose-progressmeter').setAttribute( "value", percent );
- }
- else
+ }
+ else
{
// Progress meter should be barber-pole in this case.
document.getElementById('compose-progressmeter').setAttribute( "mode", "undetermined" );
}
},
onLocationChange: function(aWebProgress, aRequest, aLocation)
{
@@ -358,17 +358,17 @@ var progressListener = {
},
QueryInterface : function(iid)
{
if (iid.equals(Components.interfaces.nsIWebProgressListener) ||
iid.equals(Components.interfaces.nsISupportsWeakReference) ||
iid.equals(Components.interfaces.nsISupports))
return this;
-
+
throw Components.results.NS_NOINTERFACE;
}
};
var defaultController =
{
supportsCommand: function(command)
{
@@ -464,17 +464,17 @@ var defaultController =
default:
// dump("##MsgCompose: command " + command + " disabled!\n");
return false;
}
},
doCommand: function(command)
- {
+ {
switch (command)
{
//File Menu
case "cmd_attachFile" : if (defaultController.isCommandEnabled(command)) AttachFile(); break;
case "cmd_attachPage" : AttachPage(); break;
case "cmd_close" : DoCommandClose(); break;
case "cmd_saveDefault" : Save(); break;
case "cmd_saveAsFile" : SaveAsFile(true); break;
@@ -549,17 +549,17 @@ function QuoteSelectedMessage()
function GetSelectedMessages()
{
if (gMsgCompose) {
var mailWindow = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService()
.QueryInterface(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("mail:3pane");
if (mailWindow) {
- return mailWindow.GetSelectedMessages();
+ return mailWindow.gFolderDisplay.selectedMessageUris;
}
}
return null;
}
function SetupCommandUpdateHandlers()
{
@@ -579,17 +579,17 @@ function CommandUpdate_MsgCompose()
if (focusedWindow == gLastWindowToHaveFocus) {
//dump("XXX skip\n");
return;
}
gLastWindowToHaveFocus = focusedWindow;
//dump("XXX update, focus on " + focusedWindow + "\n");
-
+
updateComposeItems();
}
function updateComposeItems()
{
try {
// Edit Menu
goUpdateCommand("cmd_rewrap");
@@ -641,40 +641,40 @@ function updateEditItems()
goUpdateCommand("cmd_renameAttachment");
goUpdateCommand("cmd_selectAll");
goUpdateCommand("cmd_openAttachment");
goUpdateCommand("cmd_find");
goUpdateCommand("cmd_findNext");
goUpdateCommand("cmd_findPrev");
}
-var messageComposeOfflineObserver =
+var messageComposeOfflineObserver =
{
- observe: function(subject, topic, state)
+ observe: function(subject, topic, state)
{
// sanity checks
- if (topic != "network:offline-status-changed")
+ if (topic != "network:offline-status-changed")
return;
gIsOffline = state == "offline";
MessageComposeOfflineStateChanged(gIsOffline);
try {
setupLdapAutocompleteSession();
} catch (ex) {
- // catch the exception and ignore it, so that if LDAP setup
+ // catch the exception and ignore it, so that if LDAP setup
// fails, the entire compose window stuff doesn't get aborted
}
}
}
function AddMessageComposeOfflineObserver()
{
var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
observerService.addObserver(messageComposeOfflineObserver, "network:offline-status-changed", false);
-
+
gIsOffline = gIOService.offline;
// set the initial state of the send button
MessageComposeOfflineStateChanged(gIsOffline);
}
function RemoveMessageComposeOfflineObserver()
{
var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
@@ -712,17 +712,17 @@ function MessageComposeOfflineStateChang
} catch(e) {}
}
var directoryServerObserver = {
observe: function(subject, topic, value) {
try {
setupLdapAutocompleteSession();
} catch (ex) {
- // catch the exception and ignore it, so that if LDAP setup
+ // catch the exception and ignore it, so that if LDAP setup
// fails, the entire compose window doesn't get horked
}
}
}
function AddDirectoryServerObserver(flag) {
var branch = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch2);
@@ -802,31 +802,31 @@ function setupLdapAutocompleteSession()
.classes["@mozilla.org/autocompleteSession;1?type=ldap"];
if (LDAPSession) {
try {
LDAPSession = LDAPSession.createInstance()
.QueryInterface(Components.interfaces.nsILDAPAutoCompleteSession);
} catch (ex) {dump ("ERROR: Cannot get the LDAP autocomplete session\n" + ex + "\n");}
}
}
-
- if (autocompleteDirectory && !gIsOffline) {
+
+ if (autocompleteDirectory && !gIsOffline) {
// Add observer on the directory server we are autocompleting against
// only if current server is different from previous.
- // Remove observer if current server is different from previous
+ // Remove observer if current server is different from previous
gCurrentAutocompleteDirectory = autocompleteDirectory;
if (prevAutocompleteDirectory) {
- if (prevAutocompleteDirectory != gCurrentAutocompleteDirectory) {
+ if (prevAutocompleteDirectory != gCurrentAutocompleteDirectory) {
RemoveDirectorySettingsObserver(prevAutocompleteDirectory);
AddDirectorySettingsObserver();
}
}
else
AddDirectorySettingsObserver();
-
+
// fill in the session params if there is a session
//
if (LDAPSession) {
let url = getPref(autocompleteDirectory + ".uri", true);
LDAPSession.serverURL =
Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService)
@@ -837,48 +837,48 @@ function setupLdapAutocompleteSession()
//
try {
LDAPSession.login = getPref(autocompleteDirectory + ".auth.dn", true);
} catch (ex) {
// if we don't have this pref, no big deal
}
try {
- LDAPSession.saslMechanism = getPref(autocompleteDirectory +
+ LDAPSession.saslMechanism = getPref(autocompleteDirectory +
".auth.saslmech", true);
} catch (ex) {
// don't care if we don't have this pref
}
-
+
// set the LDAP protocol version correctly
var protocolVersion;
- try {
+ try {
protocolVersion = getPref(autocompleteDirectory +
".protocolVersion");
} catch (ex) {
// if we don't have this pref, no big deal
}
if (protocolVersion == "2") {
- LDAPSession.version =
+ LDAPSession.version =
Components.interfaces.nsILDAPConnection.VERSION2;
}
// don't search on non-CJK strings shorter than this
//
- try {
+ try {
LDAPSession.minStringLength = getPref(
autocompleteDirectory + ".autoComplete.minStringLength");
} catch (ex) {
// if this pref isn't there, no big deal. just let
// nsLDAPAutoCompleteSession use its default.
}
// don't search on CJK strings shorter than this
//
- try {
+ try {
LDAPSession.cjkMinStringLength = getPref(
autocompleteDirectory + ".autoComplete.cjkMinStringLength");
} catch (ex) {
// if this pref isn't there, no big deal. just let
// nsLDAPAutoCompleteSession use its default.
}
// we don't try/catch here, because if this fails, we're outta luck
@@ -927,17 +927,17 @@ function setupLdapAutocompleteSession()
ldapFormatter.commentFormat = getPref(
autocompleteDirectory + ".description", true);
break;
case 2:
// override ldap-specific autocomplete entry?
//
try {
- ldapFormatter.commentFormat =
+ ldapFormatter.commentFormat =
getPref(autocompleteDirectory +
".autoComplete.commentFormat", true);
} catch (innerException) {
// if nothing has been specified, use the ldap
// organization field
ldapFormatter.commentFormat = "[o]";
}
break;
@@ -967,51 +967,51 @@ function setupLdapAutocompleteSession()
} catch (ex) {
// if this pref isn't there, no big deal. just let
// nsLDAPAutoCompleteSession use its default.
}
// override default search filter template?
//
- try {
+ try {
LDAPSession.filterTemplate = getPref(
autocompleteDirectory + ".autoComplete.filterTemplate",
true);
} catch (ex) {
// if this pref isn't there, no big deal. just let
// nsLDAPAutoCompleteSession use its default
}
// override default maxHits (currently 100)
//
- try {
+ try {
// XXXdmose should really use .autocomplete.maxHits,
// but there's no UI for that yet
- //
+ //
LDAPSession.maxHits = getPref(autocompleteDirectory + ".maxHits");
} catch (ex) {
- // if this pref isn't there, or is out of range, no big deal.
+ // if this pref isn't there, or is out of range, no big deal.
// just let nsLDAPAutoCompleteSession use its default.
}
if (!gSessionAdded) {
// if we make it here, we know that session initialization has
- // succeeded; add the session for all recipients, and
+ // succeeded; add the session for all recipients, and
// remember that we've done so
let maxRecipients = awGetMaxRecipients();
for (let i = 1; i <= maxRecipients; i++)
{
let autoCompleteWidget = document.getElementById("addressCol2#" + i);
if (autoCompleteWidget)
{
autoCompleteWidget.addSession(LDAPSession);
// ldap searches don't insert a default entry with the default domain appended to it
- // so reduce the minimum results for a popup to 2 in this case.
+ // so reduce the minimum results for a popup to 2 in this case.
autoCompleteWidget.minResultsForPopup = 2;
}
}
gSessionAdded = true;
}
}
} else {
@@ -1165,17 +1165,17 @@ function handleMailtoArgs(mailtoUrl)
return null;
}
function ComposeStartup(recycled, aParams)
{
var params = null; // New way to pass parameters to the compose window as a nsIMsgComposeParameters object
var args = null; // old way, parameters are passed as a string
-
+
if (aParams)
params = aParams;
else if (window.arguments && window.arguments[0]) {
try {
if (window.arguments[0] instanceof Components.interfaces.nsIMsgComposeParams)
params = window.arguments[0];
else
params = handleMailtoArgs(window.arguments[0]);
@@ -1382,17 +1382,17 @@ function ComposeStartup(recycled, aParam
} catch (e) {
dump(" Failed to startup editor: "+e+"\n");
}
}
}
gEditingDraft = gMsgCompose.compFields.draftId;
- // finally, see if we need to auto open the address sidebar.
+ // finally, see if we need to auto open the address sidebar.
var sideBarBox = document.getElementById('sidebar-box');
if (sideBarBox.getAttribute("sidebarVisible") == "true")
{
// if we aren't supposed to have the side bar hidden, make sure it is visible
if (document.getElementById("sidebar").getAttribute("src") == "")
setTimeout(toggleAddressPicker, 0); // do this on a delay so we don't hurt perf. on bringing up a new compose window
}
gAutoSaveInterval = getPref("mail.compose.autosave") ?
@@ -1401,26 +1401,31 @@ function ComposeStartup(recycled, aParam
if (gAutoSaveInterval)
gAutoSaveTimeout = setTimeout(AutoSave, gAutoSaveInterval);
gAutoSaveKickedIn = false;
}
// The new, nice, simple way of getting notified when a new editor has been created
var gMsgEditorCreationObserver =
-{
+{
observe: function(aSubject, aTopic, aData)
{
if (aTopic == "obs_documentCreated")
{
var editor = GetCurrentEditor();
if (editor && GetCurrentCommandManager() == aSubject)
{
var editorStyle = editor.QueryInterface(Components.interfaces.nsIEditorStyleSheets);
- editorStyle.addStyleSheet("chrome://messenger/skin/messageQuotes.css");
+ // We use addOverrideStyleSheet rather than addStyleSheet so that we get
+ // a synchronous load, rather than having a late-finishing async load
+ // mark our editor as modified when the user hasn't typed anything yet,
+ // but that means the sheet must not @import slow things, especially
+ // not over the network.
+ editorStyle.addOverrideStyleSheet("chrome://messenger/skin/messageQuotes.css");
InitEditor();
}
// Now that we know this document is an editor, update commands now if
// the document has focus, or next time it receives focus via
// CommandUpdate_MsgCompose()
if (gLastWindowToHaveFocus == document.commandDispatcher.focusedWindow)
updateComposeItems();
else
@@ -1449,23 +1454,23 @@ function ComposeLoad()
catch (ex) {
dump("failed to get the mail.compose.other.header pref\n");
}
AddMessageComposeOfflineObserver();
AddDirectoryServerObserver(true);
try {
- // XXX: We used to set commentColumn on the initial auto complete column after the document has loaded
+ // XXX: We used to set commentColumn on the initial auto complete column after the document has loaded
// inside of setupAutocomplete. But this happens too late for the first widget and it was never showing
// the comment field. Try to set it before the document finishes loading:
if (getPref("mail.autoComplete.commentColumn"))
document.getElementById('addressCol2#1').showCommentColumn = true;
- }
- catch (ex) {
+ }
+ catch (ex) {
// do nothing...
}
try {
SetupCommandUpdateHandlers();
// This will do migration, or create a new account if we need to.
// We also want to open the account wizard if no identities are found
var state = verifyAccounts(WizCallback, true);
@@ -1547,17 +1552,17 @@ function UpdateMailEditCharset()
if (gCharsetConvertManager) {
var charsetAlias = gCharsetConvertManager.getCharsetAlias(compFieldsCharset);
if (charsetAlias == "us-ascii")
compFieldsCharset = "ISO-8859-1"; // no menu item for "us-ascii"
}
// charset may have been set implicitly in case of reply/forward
// or use pref default otherwise
- var menuitem = document.getElementById(send_default_charset == compFieldsCharset ?
+ var menuitem = document.getElementById(send_default_charset == compFieldsCharset ?
send_default_charset : compFieldsCharset);
if (menuitem)
menuitem.setAttribute('checked', 'true');
// Set a document charset to a default mail send charset.
if (send_default_charset == compFieldsCharset)
SetDocumentCharacterSet(send_default_charset);
}
@@ -1649,16 +1654,27 @@ function GenericSendMessage( msgType )
window.openDialog("chrome://editor/content/EdSpellCheck.xul", "_blank",
"chrome,close,titlebar,modal", true, true);
}
catch(ex){}
if(window.cancelSendMessage)
return;
}
+ // Strip trailing spaces and long consecutive WSP sequences from the
+ // subject line to prevent getting only WSP chars on a folded line.
+ var fixedSubject = subject.replace(/\s{74,}/g, " ")
+ .replace(/\s*$/, "");
+ if (fixedSubject != subject)
+ {
+ subject = fixedSubject;
+ msgCompFields.subject = fixedSubject;
+ GetMsgSubjectElement().value = fixedSubject;
+ }
+
// Remind the person if there isn't a subject
if (subject == "")
{
var bundle = document.getElementById("bundle_composeMsgs");
if (gPromptService.confirmEx(
window,
bundle.getString("subjectEmptyTitle"),
bundle.getString("subjectEmptyMessage"),
@@ -1692,29 +1708,40 @@ function GenericSendMessage( msgType )
// Don't check quoted text from reply.
var blockquotes = mailBodyNode.getElementsByTagName("blockquote");
for (let i = 0; i < blockquotes.length; i++)
{
blockquotes[i].parentNode.removeChild(blockquotes[i]);
}
var mailData = mailBodyNode.textContent;
+
+ function escapeRegxpSpecials(inputString) {
+ const specials = [ ".", "\\", "^", "$", "*", "+", "?", , "|",
+ "(", ")" , "[", "]", "{", "}" ];
+ var re = new RegExp("(\\"+specials.join("|\\")+")", "g");
+ return inputString.replace(re, "\\$1");
+ }
+
var keywordFound;
for (let i = 0; i < keywordsArray.length && !keywordFound; i++)
{
- var re = new RegExp(keywordsArray[i], "i");
- keywordFound = re.test(mailData);
+ let kw = escapeRegxpSpecials(keywordsArray[i]);
+ let re = new RegExp("(([^\\s]*)\\b|\\s*)" + kw + "\\b", "i");
+ let matching = re.exec(mailData);
+ // Ignore the match if it was a URL.
+ keywordFound = matching && !(/^http|^ftp/i.test(matching[0]));
}
if (keywordFound)
{
var bundle = document.getElementById("bundle_composeMsgs");
var flags = gPromptService.BUTTON_POS_0 * gPromptService.BUTTON_TITLE_IS_STRING +
gPromptService.BUTTON_POS_1 * gPromptService.BUTTON_TITLE_IS_STRING;
- var hadForgotten = gPromptService.confirmEx(null,
+ var hadForgotten = gPromptService.confirmEx(window,
bundle.getString("attachmentReminderTitle"),
bundle.getString("attachmentReminderMsg"),
flags,
bundle.getString("attachmentReminderFalseAlarm"),
bundle.getString("attachmentReminderYesIForgot"),
null, null, {value:0});
if (hadForgotten)
return;
@@ -1811,23 +1838,23 @@ function GenericSendMessage( msgType )
}
// hook for extra compose pre-processing
var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
observerService.notifyObservers(window, "mail:composeOnSend", null);
var originalCharset = gMsgCompose.compFields.characterSet;
// Check if the headers of composing mail can be converted to a mail charset.
- if (msgType == nsIMsgCompDeliverMode.Now ||
+ if (msgType == nsIMsgCompDeliverMode.Now ||
msgType == nsIMsgCompDeliverMode.Later ||
msgType == nsIMsgCompDeliverMode.Background ||
- msgType == nsIMsgCompDeliverMode.Save ||
- msgType == nsIMsgCompDeliverMode.SaveAsDraft ||
- msgType == nsIMsgCompDeliverMode.AutoSaveAsDraft ||
- msgType == nsIMsgCompDeliverMode.SaveAsTemplate)
+ msgType == nsIMsgCompDeliverMode.Save ||
+ msgType == nsIMsgCompDeliverMode.SaveAsDraft ||
+ msgType == nsIMsgCompDeliverMode.AutoSaveAsDraft ||
+ msgType == nsIMsgCompDeliverMode.SaveAsTemplate)
{
var fallbackCharset = new Object;
// Check encoding, switch to UTF-8 if the default encoding doesn't fit
// and disable_fallback_to_utf8 isn't set for this encoding.
if (!gMsgCompose.checkCharsetConversion(getCurrentIdentity(), fallbackCharset))
{
var disableFallback = false;
try
@@ -1836,17 +1863,17 @@ function GenericSendMessage( msgType )
}
catch (e) {}
if (disableFallback)
msgCompFields.needToCheckCharset = false;
else
fallbackCharset.value = "UTF-8";
}
- if (fallbackCharset &&
+ if (fallbackCharset &&
fallbackCharset.value && fallbackCharset.value != "")
gMsgCompose.SetDocumentCharset(fallbackCharset.value);
}
try {
// just before we try to send the message, fire off the compose-send-message event for listeners
// such as smime so they can do any pre-security work such as fetching certificates before sending
var event = document.createEvent('UIEvents');
@@ -1863,17 +1890,17 @@ function GenericSendMessage( msgType )
{
gWindowLocked = true;
disableEditableFields();
updateComposeItems();
}
// if we're auto saving, mark the body as not changed here, and not
// when the save is done, because the user might change it between now
// and when the save is done.
- else
+ else
SetContentAndBodyAsUnmodified();
var progress = Components.classes["@mozilla.org/messenger/progress;1"].createInstance(Components.interfaces.nsIMsgProgress);
if (progress)
{
progress.registerListener(progressListener);
gSendOrSaveOperationInProgress = true;
}
@@ -1909,17 +1936,17 @@ function CheckValidEmailAddress(to, cc,
if (invalidStr)
{
var bundle = document.getElementById("bundle_composeMsgs");
var errorTitle = bundle.getString("sendMsgTitle");
var errorMsg = bundle.getFormattedString("addressInvalid", [invalidStr], 1);
if (gPromptService)
gPromptService.alert(window, errorTitle, errorMsg);
return false;
- }
+ }
return true;
}
function SendMessage()
{
let sendInBackground =
Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch)
@@ -1932,17 +1959,17 @@ function SendMessage()
function SendMessageWithCheck()
{
var warn = getPref("mail.warn_on_send_accel_key");
if (warn) {
var checkValue = {value:false};
var bundle = document.getElementById("bundle_composeMsgs");
- var buttonPressed = gPromptService.confirmEx(window,
+ var buttonPressed = gPromptService.confirmEx(window,
bundle.getString('sendMessageCheckWindowTitle'),
bundle.getString('sendMessageCheckLabel'),
(gPromptService.BUTTON_TITLE_IS_STRING * gPromptService.BUTTON_POS_0) +
(gPromptService.BUTTON_TITLE_CANCEL * gPromptService.BUTTON_POS_1),
bundle.getString('sendMessageCheckSendButtonLabel'),
null, null,
bundle.getString('CheckMsg'),
checkValue);
@@ -2030,17 +2057,17 @@ function MessageFcc(aFolder)
function updatePriorityMenu()
{
if (gMsgCompose)
{
var msgCompFields = gMsgCompose.compFields;
if (msgCompFields && msgCompFields.priority)
{
- var priorityMenu = document.getElementById('priorityMenu' );
+ var priorityMenu = document.getElementById('priorityMenu' );
priorityMenu.getElementsByAttribute( "checked", 'true' )[0].removeAttribute('checked');
priorityMenu.getElementsByAttribute( "value", msgCompFields.priority )[0].setAttribute('checked', 'true');
}
}
}
function updatePriorityToolbarButton(newPriorityValue)
{
@@ -2189,29 +2216,29 @@ function InitLanguageMenu()
// sort by locale-aware collation
dictList.sort(
function compareFn(a, b)
{
return a[0].localeCompare(b[0]);
}
);
-
+
// Remove any languages from the list.
while (languageMenuList.hasChildNodes())
languageMenuList.removeChild(languageMenuList.firstChild);
for (let i = 0; i < count; i++)
{
var item = document.createElement("menuitem");
item.setAttribute("label", dictList[i][0]);
item.setAttribute("value", dictList[i][1]);
item.setAttribute('type', 'radio');
languageMenuList.appendChild(item);
- }
+ }
}
function OnShowDictionaryMenu(aTarget)
{
InitLanguageMenu();
var curLang = getPref("spellchecker.dictionary", true);
var languages = aTarget.getElementsByAttribute("value", curLang);
if (languages.length > 0)
@@ -2493,28 +2520,28 @@ function RemoveDraft()
keyArray[0] = msgKey;
imapFolder.storeImapFlags(8, true, keyArray, 1, null);
}
} catch (ex) {}
}
function SetContentAndBodyAsUnmodified()
{
- gMsgCompose.bodyModified = false;
+ gMsgCompose.bodyModified = false;
gContentChanged = false;
}
function ReleaseAutoCompleteState()
{
let maxRecipients = awGetMaxRecipients();
for (let i = 1; i <= maxRecipients; i++)
document.getElementById("addressCol2#" + i).removeSession(gLDAPSession);
gSessionAdded = false;
- gLDAPSession = null;
+ gLDAPSession = null;
}
function MsgComposeCloseWindow(recycleIt)
{
if (gMsgCompose)
gMsgCompose.CloseWindow(recycleIt);
else
window.close();
@@ -2733,24 +2760,24 @@ function RemoveAllAttachments()
var child;
var bucket = document.getElementById("attachmentBucket");
while (bucket.getRowCount())
{
child = bucket.removeItemAt(bucket.getRowCount() - 1);
// Let's release the attachment object hold by the node else it won't go away until the window is destroyed
child.attachment = null;
}
-
+
ChangeAttachmentBucketVisibility(true);
}
function ChangeAttachmentBucketVisibility(aHideBucket)
{
document.getElementById("attachments-box").collapsed = aHideBucket;
- document.getElementById("attachmentbucket-sizer").collapsed = aHideBucket;
+ document.getElementById("attachmentbucket-sizer").collapsed = aHideBucket;
}
function RemoveSelectedAttachment()
{
var child;
var bucket = document.getElementById("attachmentBucket");
if (bucket.selectedItems.length > 0) {
for (let i = bucket.selectedCount - 1; i >= 0; i--)
@@ -2797,37 +2824,37 @@ function FocusOnFirstAttachment()
if (bucketList && bucketList.getRowCount())
bucketList.selectedIndex = 0;
}
function AttachmentElementHasItems()
{
var element = document.getElementById("attachmentBucket");
return element ? element.getRowCount() : 0;
-}
+}
function OpenSelectedAttachment()
{
var child;
var bucket = document.getElementById("attachmentBucket");
- if (bucket.selectedItems.length == 1)
+ if (bucket.selectedItems.length == 1)
{
var attachmentUrl = bucket.getSelectedItem(0).attachment.url;
var messagePrefix = /^mailbox-message:|^imap-message:|^news-message:/i;
if (messagePrefix.test(attachmentUrl))
{
// we must be dealing with a forwarded attachment, treat this special
var messenger = Components.classes["@mozilla.org/messenger;1"].createInstance();
messenger = messenger.QueryInterface(Components.interfaces.nsIMessenger);
var msgHdr = messenger.messageServiceFromURI(attachmentUrl).messageURIToMsgHdr(attachmentUrl);
if (msgHdr)
{
var folderUri = msgHdr.folder.folderURL;
- window.openDialog("chrome://messenger/content/messageWindow.xul", "_blank", "all,chrome,dialog=no,status,toolbar",
+ window.openDialog("chrome://messenger/content/messageWindow.xul", "_blank", "all,chrome,dialog=no,status,toolbar",
attachmentUrl, folderUri, null );
}
}
else
{
// turn the url into a nsIURL object then open it
var url = gIOService.newURI(attachmentUrl, null, null);
@@ -2836,17 +2863,17 @@ function OpenSelectedAttachment()
if (url)
{
var channel = gIOService.newChannelFromURI(url);
if (channel)
{
var uriLoader = Components.classes["@mozilla.org/uriloader;1"].getService(Components.interfaces.nsIURILoader);
uriLoader.openURI(channel, true, new nsAttachmentOpener());
} // if channel
- } // if url
+ } // if url
}
} // if one attachment selected
}
function nsAttachmentOpener()
{
}
@@ -2984,17 +3011,17 @@ function DetermineConvertibility()
} catch(ex) {}
return nsIMsgCompConvertible.No;
}
function LoadIdentity(startup)
{
var identityElement = document.getElementById("msgIdentity");
var prevIdentity = gCurrentIdentity;
-
+
if (identityElement) {
var idKey = identityElement.value;
gCurrentIdentity = gAccountManager.getIdentity(idKey);
// set the account name on the menu list value.
if (identityElement.selectedItem)
identityElement.setAttribute('accountname', identityElement.selectedItem.getAttribute('accountname'));
@@ -3017,17 +3044,17 @@ function LoadIdentity(startup)
var newReplyTo = gCurrentIdentity.replyTo;
var newBcc = "";
var newReceipt = gCurrentIdentity.requestReturnReceipt;
var newDSN = gCurrentIdentity.DSN;
var newAttachVCard = gCurrentIdentity.attachVCard;
if (gCurrentIdentity.doBcc)
- newBcc += gCurrentIdentity.doBccList;
+ newBcc += gCurrentIdentity.doBccList;
var needToCleanUp = false;
var msgCompFields = gMsgCompose.compFields;
if (!gReceiptOptionChanged &&
prevReceipt == msgCompFields.returnReceipt &&
prevReceipt != newReceipt)
{
@@ -3084,17 +3111,17 @@ function LoadIdentity(startup)
AddDirectoryServerObserver(false);
if (!startup) {
if (getPref("mail.autoComplete.highlightNonMatches"))
document.getElementById('addressCol2#1').highlightNonMatches = true;
try {
setupLdapAutocompleteSession();
} catch (ex) {
- // catch the exception and ignore it, so that if LDAP setup
+ // catch the exception and ignore it, so that if LDAP setup
// fails, the entire compose window doesn't end up horked
}
addRecipientsToIgnoreList(gCurrentIdentity.identityName); // only do this if we aren't starting up....it gets done as part of startup already
}
}
}
@@ -3102,47 +3129,47 @@ function setupAutocomplete()
{
var autoCompleteWidget = document.getElementById("addressCol2#1");
// When autocompleteToMyDomain is off there is no default entry with the domain
// appended so reduce the minimum results for a popup to 2 in this case.
if (!gCurrentIdentity.autocompleteToMyDomain)
autoCompleteWidget.minResultsForPopup = 2;
// if the pref is set to turn on the comment column, honor it here.
- // this element then gets cloned for subsequent rows, so they should
+ // this element then gets cloned for subsequent rows, so they should
// honor it as well
//
- try
+ try
{
if (getPref("mail.autoComplete.highlightNonMatches"))
autoCompleteWidget.highlightNonMatches = true;
if (getPref("mail.autoComplete.commentColumn"))
autoCompleteWidget.showCommentColumn = true;
- } catch (ex)
+ } catch (ex)
{
// if we can't get this pref, then don't show the columns (which is
// what the XUL defaults to)
}
-
- if (!gSetupLdapAutocomplete)
+
+ if (!gSetupLdapAutocomplete)
{
- try
+ try
{
setupLdapAutocompleteSession();
- } catch (ex)
+ } catch (ex)
{
- // catch the exception and ignore it, so that if LDAP setup
+ // catch the exception and ignore it, so that if LDAP setup
// fails, the entire compose window doesn't end up horked
}
}
}
function subjectKeyPress(event)
-{
+{
switch(event.keyCode) {
case KeyEvent.DOM_VK_TAB:
if (!event.shiftKey) {
SetMsgBodyFrameFocus();
event.preventDefault();
}
break;
case KeyEvent.DOM_VK_RETURN:
@@ -3156,17 +3183,17 @@ function AttachmentBucketClicked(event)
event.currentTarget.focus();
if (event.button != 0)
return;
if (event.originalTarget.localName == "listboxbody")
goDoCommand('cmd_attachFile');
else if (event.originalTarget.localName == "listitem" && event.detail == 2)
- OpenSelectedAttachment();
+ OpenSelectedAttachment();
}
// we can drag and drop addresses, files, messages and urls into the compose envelope
var envelopeDragObserver = {
canHandleMultipleItems: true,
onDrop: function (aEvent, aData, aDragSession)
@@ -3195,33 +3222,33 @@ var envelopeDragObserver = {
.getService(Components.interfaces.nsIIOService)
.getProtocolHandler("file")
.QueryInterface(Components.interfaces.nsIFileProtocolHandler);
rawData = fileHandler.getURLSpecFromFile(rawData);
}
else
{
var separator = rawData.indexOf("\n");
- if (separator != -1)
+ if (separator != -1)
{
prettyName = rawData.substr(separator+1);
rawData = rawData.substr(0,separator);
}
}
if (DuplicateFileAlreadyAttached(rawData))
{
dump("Skipping file "+rawData+"; already attached!\n");
}
else
{
var isValid = true;
if (item.flavour.contentType == "text/x-moz-url") {
// if this is a url (or selected text)
- // see if it's a valid url by checking
+ // see if it's a valid url by checking
// if we can extract a scheme
// using the ioservice
//
// also skip mailto:, since it doesn't make sense
// to attach and send mailto urls
try {
var scheme = gIOService.extractScheme(rawData);
// don't attach mailto: urls
@@ -3271,17 +3298,17 @@ var envelopeDragObserver = {
},
getSupportedFlavours: function ()
{
var flavourSet = new FlavourSet();
flavourSet.appendFlavour("text/x-moz-url");
flavourSet.appendFlavour("text/x-moz-message");
flavourSet.appendFlavour("application/x-moz-file", "nsIFile");
- flavourSet.appendFlavour("text/x-moz-address");
+ flavourSet.appendFlavour("text/x-moz-address");
return flavourSet;
}
};
function DisplaySaveFolderDlg(folderURI)
{
try
{
@@ -3406,17 +3433,17 @@ function WhichElementHasFocus()
return currentNode;
currentNode = currentNode.parentNode;
}
return null;
}
-// Function that performs the logic of switching focus from
+// Function that performs the logic of switching focus from
// one element to another in the mail compose window.
// The default element to switch to when going in either
// direction (shift or no shift key pressed), is the
// AddressingWidgetTreeElement.
//
// The only exception is when the MsgHeadersToolbar is
// collapsed, then the focus will always be on the body of
// the message.
@@ -3466,17 +3493,17 @@ function SwitchElementFocus(event)
}
}
function toggleAddressPicker()
{
var sidebarBox = document.getElementById("sidebar-box");
var sidebarSplitter = document.getElementById("sidebar-splitter");
var elt = document.getElementById("viewAddressPicker");
- if (sidebarBox.hidden)
+ if (sidebarBox.hidden)
{
sidebarBox.hidden = false;
sidebarSplitter.hidden = false;
elt.setAttribute("checked","true");
var sidebar = document.getElementById("sidebar");
var sidebarUrl = sidebar.getAttribute("src");
// if we have yet to initialize the src url on the sidebar than go ahead and do so now...
@@ -3503,71 +3530,76 @@ function AddRecipient(recipientType, add
}
function loadHTMLMsgPrefs()
{
var fontFace;
var fontSize;
var textColor;
var bgColor;
-
- try {
+
+ try {
fontFace = getPref("msgcompose.font_face", true);
doStatefulCommand('cmd_fontFace', fontFace);
} catch (e) {}
- try {
+ try {
fontSize = getPref("msgcompose.font_size");
EditorSetFontSize(fontSize);
} catch (e) {}
var bodyElement = GetBodyElement();
- try {
+ try {
textColor = getPref("msgcompose.text_color");
if (!bodyElement.getAttribute("text"))
{
bodyElement.setAttribute("text", textColor);
gDefaultTextColor = textColor;
- document.getElementById("cmd_fontColor").setAttribute("state", textColor);
+ document.getElementById("cmd_fontColor").setAttribute("state", textColor);
onFontColorChange();
}
} catch (e) {}
- try {
+ try {
bgColor = getPref("msgcompose.background_color");
if (!bodyElement.getAttribute("bgcolor"))
{
bodyElement.setAttribute("bgcolor", bgColor);
gDefaultBackgroundColor = bgColor;
document.getElementById("cmd_backgroundColor").setAttribute("state", bgColor);
onBackgroundColorChange();
}
} catch (e) {}
}
function AutoSave()
{
- if (gMsgCompose.editor && (gContentChanged || gMsgCompose.bodyModified)
+ if (gMsgCompose.editor && (gContentChanged || gMsgCompose.bodyModified)
&& !gSendOrSaveOperationInProgress)
{
GenericSendMessage(nsIMsgCompDeliverMode.AutoSaveAsDraft);
gAutoSaveKickedIn = true;
}
gAutoSaveTimeout = setTimeout(AutoSave, gAutoSaveInterval);
}
function InitEditor()
{
var editor = GetCurrentEditor();
editor.QueryInterface(nsIEditorStyleSheets);
+ // We use addOverrideStyleSheet rather than addStyleSheet so that we get
+ // a synchronous load, rather than having a late-finishing async load
+ // mark our editor as modified when the user hasn't typed anything yet,
+ // but that means the sheet must not @import slow things, especially
+ // not over the network.
editor.addOverrideStyleSheet("chrome://messenger/content/composerOverlay.css");
gMsgCompose.initEditor(editor, window.content);
-
+
InlineSpellCheckerUI.init(editor);
enableInlineSpellCheck(getPref("mail.spellcheck.inline"));
document.getElementById('menu_inlineSpellCheck').setAttribute('disabled', !InlineSpellCheckerUI.canSpellCheck);
}
function enableInlineSpellCheck(aEnableInlineSpellCheck)
{
InlineSpellCheckerUI.enabled = aEnableInlineSpellCheck;
--- a/mail/components/compose/jar.mn
+++ b/mail/components/compose/jar.mn
@@ -1,9 +1,9 @@
messenger.jar:
% overlay chrome://editor/content/EdImageOverlay.xul chrome://messenger/content/messengercompose/mailComposeEditorOverlay.xul
% overlay chrome://editor/content/EdLinkProps.xul chrome://messenger/content/messengercompose/mailComposeEditorOverlay.xul
* content/messenger/messengercompose/messengercompose.xul (content/messengercompose.xul)
-* content/messenger/messengercompose/MsgComposeCommands.js (content/MsgComposeCommands.js)
+ content/messenger/messengercompose/MsgComposeCommands.js (content/MsgComposeCommands.js)
* content/messenger/messengercompose/addressingWidgetOverlay.js (content/addressingWidgetOverlay.js)
comm.jar:
*+ content/editor/editorOverlay.xul (content/editorOverlay.xul)
--- a/mail/extensions/mailviews/content/msgViewPickerOverlay.js
+++ b/mail/extensions/mailviews/content/msgViewPickerOverlay.js
@@ -1,10 +1,9 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
@@ -33,428 +32,267 @@
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-// menuitem value constants
+Components.utils.import("resource://app/modules/mailViewManager.js");
+
+// these constants are now authoritatively defined in mailViewManager.js (above)
// tag views have kViewTagMarker + their key as value
-const kViewItemAll = 0;
-const kViewItemUnread = 1;
-const kViewItemTags = 2; // former labels used values 2-6
-const kViewItemNotDeleted = 3;
-const kViewItemVirtual = 7;
-const kViewItemCustomize = 8;
-const kViewItemFirstCustom = 9;
+const kViewItemAll = MailViewConstants.kViewItemAll;
+const kViewItemUnread = MailViewConstants.kViewItemUnread;
+const kViewItemTags = MailViewConstants.kViewItemTags; // former labels used values 2-6
+const kViewItemNotDeleted = MailViewConstants.kViewItemNotDeleted;
+// not a real view! a sentinel value to pop up a dialog
+const kViewItemVirtual = MailViewConstants.kViewItemVirtual;
+// not a real view! a sentinel value to pop up a dialog
+const kViewItemCustomize = MailViewConstants.kViewItemCustomize;
+const kViewItemFirstCustom = MailViewConstants.kViewItemFirstCustom;
-const kViewCurrent = "current-view";
-const kViewCurrentTag = "current-view-tag";
-const kViewTagMarker = ":";
+const kViewCurrent = MailViewConstants.kViewCurrent;
+const kViewCurrentTag = MailViewConstants.kViewCurrentTag;
+const kViewTagMarker = MailViewConstants.kViewTagMarker;
+/**
+ * A reference to the nsIMsgMailViewList service that tracks custom mail views.
+ */
var gMailViewList = null;
-var gCurrentViewValue = kViewItemAll;
-var gCurrentViewLabel = "";
-var gSaveDefaultSVTerms;
var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
var nsMsgSearchAttrib = Components.interfaces.nsMsgSearchAttrib;
var nsMsgSearchOp = Components.interfaces.nsMsgSearchOp;
+var nsMsgMessageFlags = Components.interfaces.nsMsgMessageFlags;
// perform the view/action requested by the aValue string
// and set the view picker label to the aLabel string
-function ViewChange(aValue, aLabel)
+function ViewChange(aValue)
{
if (aValue == kViewItemCustomize || aValue == kViewItemVirtual)
{
// restore to the previous view value, in case they cancel
- UpdateViewPicker(gCurrentViewValue, gCurrentViewLabel);
+ ViewPickerBinding.updateDisplay();
if (aValue == kViewItemCustomize)
LaunchCustomizeDialog();
else
- {
- // Thunderbird uses the folder pane for this.
- if ("gFolderTreeController" in window)
- gFolderTreeController.newVirtualFolder(gCurrentViewLabel,
- gSaveDefaultSVTerms);
- else
- openNewVirtualFolderDialogWithArgs(gCurrentViewLabel, gSaveDefaultSVTerms);
- }
+ gFolderTreeController.newVirtualFolder(
+ ViewPickerBinding.currentViewLabel,
+ gFolderDisplay.view.search.viewTerms);
return;
}
- // persist the view
- gCurrentViewValue = aValue;
- gCurrentViewLabel = aLabel;
- SetMailViewForFolder(GetFirstSelectedMsgFolder(), gCurrentViewValue)
- UpdateViewPicker(gCurrentViewValue, gCurrentViewLabel);
-
// tag menuitem values are of the form :<keyword>
if (isNaN(aValue))
{
// split off the tag key
var tagkey = aValue.substr(kViewTagMarker.length);
- ViewTagKeyword(tagkey);
+ gFolderDisplay.view.setMailView(kViewItemTags, tagkey);
}
else
{
var numval = Number(aValue);
- switch (numval)
- {
- case kViewItemAll: // View All
- gDefaultSearchViewTerms = null;
- break;
- case kViewItemUnread: // Unread
- ViewNewMail();
- break;
- case kViewItemNotDeleted: // Not deleted
- ViewNotDeletedMail();
- break;
- default:
- // for legacy reasons, custom views start at index 9
- LoadCustomMailView(numval - kViewItemFirstCustom);
- break;
- }
+ gFolderDisplay.view.setMailView(numval, null);
}
- gSaveDefaultSVTerms = gDefaultSearchViewTerms;
- onEnterInSearchBar();
- gQSViewIsDirty = true;
+ ViewPickerBinding.updateDisplay();
}
function ViewChangeByMenuitem(aMenuitem)
{
// Mac View menu menuitems don't have XBL bindings
- ViewChange(aMenuitem.getAttribute("value"), aMenuitem.getAttribute("label"));
-}
-
-
-function ViewChangeByValue(aValue)
-{
- ViewChange(aValue, GetLabelForValue(aValue));
-}
-
-function ViewChangeByFolder(aFolder)
-{
- var result = GetMailViewForFolder(aFolder);
- ViewChangeByValue(result);
-}
-
-function GetLabelForValue(aValue)
-{
- var label = "";
- let viewPickerPopup = document.getElementById("viewPickerPopup");
- if (viewPickerPopup)
- {
- // grab the label for the menulist from one of its menuitems
- var selectedItems = viewPickerPopup.getElementsByAttribute("value", aValue);
- if (!selectedItems || !selectedItems.length)
- {
- // we may have a new item
- RefreshAllViewPopups(viewPickerPopup, true);
- selectedItems = viewPickerPopup.getElementsByAttribute("value", aValue);
- }
- label = selectedItems && selectedItems.length && selectedItems.item(0).label;
- }
- return label;
-}
-
-function UpdateViewPickerByValue(aValue)
-{
- UpdateViewPicker(aValue, GetLabelForValue(aValue));
+ ViewChange(aMenuitem.getAttribute("value"));
}
-function UpdateViewPicker(aValue, aLabel)
-{
- var viewPicker = document.getElementById("viewPicker");
- if (viewPicker)
- {
- viewPicker.value = aValue;
- viewPicker.setAttribute("label", aLabel);
- }
-}
+/**
+ * Mediates interaction with the #viewPickerPopup. In theory this should be
+ * an XBL binding, but for the insanity where the view picker may not be
+ * visible at all times (or ever). No view picker widget, no binding.
+ */
+var ViewPickerBinding = {
+ _init: function ViewPickerBinding_init() {
+ window.addEventListener(
+ "MailViewChanged",
+ function(aEvent) { ViewPickerBinding.updateDisplay(aEvent); },
+ false);
+ },
-function GetFolderInfo(aFolder)
-{
- if (aFolder)
- {
- var db = aFolder.msgDatabase;
- if (db)
- return db.dBFolderInfo;
- }
- return null;
-}
-
+ /**
+ * Return true if the view picker is visible. This is used by the
+ * FolderDisplayWidget to know whether or not to actually use mailviews. (The
+ * idea is that if we are not visible, then it would be confusing to the user
+ * if we filtered their mail since they would have no feedback about this and
+ * no way to change it.)
+ */
+ get isVisible() {
+ return document.getElementById("viewPicker") != null;
+ },
-function GetMailViewForFolder(aFolder)
-{
- var val = "";
- var folderInfo = GetFolderInfo(aFolder);
- if (folderInfo)
- {
- val = folderInfo.getCharProperty(kViewCurrentTag);
- if (!val)
- {
- // no new view value, thus using the old
- var numval = folderInfo.getUint32Property(kViewCurrent, kViewItemAll);
- // and migrate it, if it's a former label view (label views used values 2-6)
- if ((kViewItemTags <= numval) && (numval < kViewItemVirtual))
- val = kViewTagMarker + "$label" + (val - 1);
- else
- val = numval;
+ /**
+ * Return the string value representing the current mail view value as
+ * understood by the view picker widgets. The value is the index for
+ * everything but tags. for tags it's the ":"-prefixed tagname.
+ */
+ get currentViewValue() {
+ if (gFolderDisplay.view.mailViewIndex == kViewItemTags)
+ return kViewTagMarker + gFolderDisplay.view.mailViewData;
+ else
+ return gFolderDisplay.view.mailViewIndex + "";
+ },
+
+ /**
+ * @return The label for the current mail view value.
+ */
+ get currentViewLabel() {
+ let viewPicker = document.getElementById("viewPicker");
+ return viewPicker.getAttribute("label");
+ },
+
+ /**
+ * The effective view has changed, update the widget.
+ */
+ updateDisplay: function ViewPickerBinding_updateDisplay(aEvent, aGiveUpIfNotFound) {
+ let viewPicker = document.getElementById("viewPicker");
+ if (viewPicker) {
+ let value = this.currentViewValue;
+
+ let viewPickerPopup = document.getElementById("viewPickerPopup");
+ let selectedItems =
+ viewPickerPopup.getElementsByAttribute("value", value);
+ if (!selectedItems || !selectedItems.length)
+ {
+ // we may have a new item, so refresh to make it show up
+ RefreshAllViewPopups(viewPickerPopup, true);
+ selectedItems = viewPickerPopup.getElementsByAttribute("value", value);
+ }
+ viewPicker.setAttribute("label",
+ selectedItems && selectedItems.length &&
+ selectedItems.item(0).getAttribute("label"));
}
- }
- return val;
-}
-
-
-function SetMailViewForFolder(aFolder, aValue)
-{
- var folderInfo = GetFolderInfo(aFolder);
- if (folderInfo)
- {
- // we can't map tags back to labels in general,
- // so set view to all for backwards compatibility in this case
- folderInfo.setUint32Property (kViewCurrent, isNaN(aValue) ? kViewItemAll : aValue);
- folderInfo.setCharProperty(kViewCurrentTag, aValue);
- }
-}
-
+ },
+};
+ViewPickerBinding._init();
function LaunchCustomizeDialog()
{
OpenOrFocusWindow({}, "mailnews:mailviewlist", "chrome://messenger/content/mailViewList.xul");
}
-
-function LoadCustomMailView(index)
-{
- PrepareForViewChange();
- var searchTermsArrayForQS = CreateGroupedSearchTerms(gMailViewList.getMailViewAt(index).searchTerms);
- createSearchTermsWithList(searchTermsArrayForQS);
- AddVirtualFolderTerms(searchTermsArrayForQS);
- gDefaultSearchViewTerms = searchTermsArrayForQS;
-}
-
-
-function ViewTagKeyword(keyword)
+/**
+ * All of these Refresh*ViewPopup* methods have to deal with two menu
+ * variations. They are accessible from the "View... Messages" menu as well as
+ * the view picker menu list in the toolbar. aIsMenulist will be false in the
+ * former case and true in the latter case.
+ */
+function RefreshAllViewPopups(aViewPopup)
{
- PrepareForViewChange();
-
- // create an i supports array to store our search terms
- var searchTermsArray = Components.classes["@mozilla.org/supports-array;1"]
- .createInstance(Components.interfaces.nsISupportsArray);
- var term = gSearchSession.createTerm();
- var value = term.value;
-
- value.str = keyword;
- value.attrib = nsMsgSearchAttrib.Keywords;
- term.value = value;
- term.attrib = nsMsgSearchAttrib.Keywords;
- term.op = nsMsgSearchOp.Contains;
- term.booleanAnd = true;
-
- searchTermsArray.AppendElement(term);
- AddVirtualFolderTerms(searchTermsArray);
- createSearchTermsWithList(searchTermsArray);
- gDefaultSearchViewTerms = searchTermsArray;
+ RefreshViewPopup(aViewPopup);
+ var menupopups = aViewPopup.getElementsByTagName("menupopup");
+ if (menupopups.length > 1)
+ {
+ // when we have menupopups, we assume both tags and custom views are there
+ RefreshTagsPopup(menupopups[0]);
+ RefreshCustomViewsPopup(menupopups[1]);
+ }
}
-function ViewNewMail()
+function RefreshViewPopup(aViewPopup)
{
- PrepareForViewChange();
-
- // create an i supports array to store our search terms
- var searchTermsArray = Components.classes["@mozilla.org/supports-array;1"]
- .createInstance(Components.interfaces.nsISupportsArray);
- var term = gSearchSession.createTerm();
- var value = term.value;
+ // mark default views if selected
+ let currentViewValue = ViewPickerBinding.currentViewValue;
- value.status = 1;
- value.attrib = nsMsgSearchAttrib.MsgStatus;
- term.value = value;
- term.attrib = nsMsgSearchAttrib.MsgStatus;
- term.op = nsMsgSearchOp.Isnt;
- term.booleanAnd = true;
- searchTermsArray.AppendElement(term);
-
- AddVirtualFolderTerms(searchTermsArray);
-
- createSearchTermsWithList(searchTermsArray);
- // not quite right - these want to be just the view terms...but it might not matter.
- gDefaultSearchViewTerms = searchTermsArray;
-}
-
-
-function ViewNotDeletedMail()
-{
- PrepareForViewChange();
+ var viewAll = aViewPopup.getElementsByAttribute("value", kViewItemAll)[0];
+ viewAll.setAttribute("checked", currentViewValue == kViewItemAll);
+ let viewUnread =
+ aViewPopup.getElementsByAttribute("value", kViewItemUnread)[0];
+ viewUnread.setAttribute("checked", currentViewValue == kViewItemUnread);
- // create an i supports array to store our search terms
- var searchTermsArray = Components.classes["@mozilla.org/supports-array;1"]
- .createInstance(Components.interfaces.nsISupportsArray);
- var term = gSearchSession.createTerm();
- var value = term.value;
-
- value.status = 0x00200000;
- value.attrib = nsMsgSearchAttrib.MsgStatus;
- term.value = value;
- term.attrib = nsMsgSearchAttrib.MsgStatus;
- term.op = nsMsgSearchOp.Isnt;
- term.booleanAnd = true;
- searchTermsArray.AppendElement(term);
-
- AddVirtualFolderTerms(searchTermsArray);
+ let viewNotDeleted =
+ aViewPopup.getElementsByAttribute("value", kViewItemNotDeleted)[0];
+ var folderArray = GetSelectedMsgFolders();
+ if (folderArray.length == 0)
+ return;
- createSearchTermsWithList(searchTermsArray);
- // not quite right - these want to be just the view terms...but it might not matter.
- gDefaultSearchViewTerms = searchTermsArray;
-}
-
-
-function AddVirtualFolderTerms(searchTermsArray)
-{
- // add in any virtual folder terms
- var virtualFolderSearchTerms = (gVirtualFolderTerms || gXFVirtualFolderTerms);
- if (virtualFolderSearchTerms)
+ // only show the "Not Deleted" item for IMAP servers that are using the IMAP
+ // delete model
+ viewNotDeleted.setAttribute("hidden", true);
+ var msgFolder = folderArray[0];
+ var server = msgFolder.server;
+ if (server.type == "imap")
{
- var isupports = null;
- var searchTerm;
- var termsArray = virtualFolderSearchTerms.QueryInterface(Components.interfaces.nsISupportsArray);
- for (var i = 0; i < termsArray.Count(); i++)
- {
- isupports = termsArray.GetElementAt(i);
- searchTerm = isupports.QueryInterface(Components.interfaces.nsIMsgSearchTerm);
- searchTermsArray.AppendElement(searchTerm);
+ let imapServer =
+ server.QueryInterface(Components.interfaces.nsIImapIncomingServer);
+ if (imapServer.deleteModel == 0) { // nsMsgImapDeleteModels.IMAPDelete
+ viewNotDeleted.setAttribute("hidden", false);
+ viewNotDeleted.setAttribute("checked",
+ currentViewValue == kViewItemNotDeleted);
}
}
}
-function PrepareForViewChange()
-{
- // this is a problem - it saves the current view in gPreQuickSearchView
- // then we eventually call onEnterInSearchBar, and we think we need to restore the pre search view!
- initializeSearchBar();
- ClearThreadPaneSelection();
- ClearMessagePane();
-}
-
-
-// refresh view popup and its subpopups
-function RefreshAllViewPopups(aViewPopup, aIsMenulist)
-{
- RefreshViewPopup(aViewPopup, aIsMenulist);
- var menupopups = aViewPopup.getElementsByTagName("menupopup");
- if (menupopups.length > 1)
- {
- // when we have menupopups, we assume both tags and custom views are there
- RefreshTagsPopup(menupopups[0], aIsMenulist);
- RefreshCustomViewsPopup(menupopups[1], aIsMenulist);
- }
-}
-
-
-function RefreshViewPopup(aViewPopup, aIsMenulist)
-{
- // mark default views if selected
- if (!aIsMenulist)
- {
- var viewAll = aViewPopup.getElementsByAttribute("value", kViewItemAll)[0];
- viewAll.setAttribute("checked", gCurrentViewValue == kViewItemAll);
- var viewUnread = aViewPopup.getElementsByAttribute("value", kViewItemUnread)[0];
- viewUnread.setAttribute("checked", gCurrentViewValue == kViewItemUnread);
-
- var viewNotDeleted = aViewPopup.getElementsByAttribute("value", kViewItemNotDeleted)[0];
- var folderArray = GetSelectedMsgFolders();
- if (folderArray.length == 0)
- return;
-
- // only show the "Not Deleted" item for IMAP servers that are using the IMAP delete model
- viewNotDeleted.setAttribute("hidden", true);
- var msgFolder = folderArray[0];
- var server = msgFolder.server;
- if (server.type == "imap")
- {
- var imapServer = server.QueryInterface(Components.interfaces.nsIImapIncomingServer);
- if (imapServer.deleteModel == 0) // nsMsgImapDeleteModels.IMAPDelete == 0
- {
- viewNotDeleted.setAttribute("hidden", false);
- viewNotDeleted.setAttribute("checked", gCurrentViewValue == kViewItemNotDeleted);
- }
- }
- }
-}
-
-
-function RefreshCustomViewsPopup(aMenupopup, aIsMenulist)
+function RefreshCustomViewsPopup(aMenupopup)
{
// for each mail view in the msg view list, add an entry in our combo box
if (!gMailViewList)
gMailViewList = Components.classes["@mozilla.org/messenger/mailviewlist;1"]
.getService(Components.interfaces.nsIMsgMailViewList);
// remove all menuitems
while (aMenupopup.hasChildNodes())
aMenupopup.removeChild(aMenupopup.lastChild);
// now rebuild the list
- var currentView = isNaN(gCurrentViewValue) ? kViewItemAll : Number(gCurrentViewValue);
+ var currentView = ViewPickerBinding.currentViewValue;
var numItems = gMailViewList.mailViewCount;
for (var i = 0; i < numItems; ++i)
{
var viewInfo = gMailViewList.getMailViewAt(i);
var menuitem = document.createElement("menuitem");
menuitem.setAttribute("label", viewInfo.prettyName);
menuitem.setAttribute("value", kViewItemFirstCustom + i);
- if (!aIsMenulist)
- {
- menuitem.setAttribute("type", "radio");
- if (kViewItemFirstCustom + i == currentView)
- menuitem.setAttribute("checked", true);
- }
+ menuitem.setAttribute("type", "radio");
+ if (kViewItemFirstCustom + i == currentView)
+ menuitem.setAttribute("checked", true);
aMenupopup.appendChild(menuitem);
}
}
-function RefreshTagsPopup(aMenupopup, aIsMenulist)
+function RefreshTagsPopup(aMenupopup)
{
// remove all menuitems
while (aMenupopup.hasChildNodes())
aMenupopup.removeChild(aMenupopup.lastChild);
// create tag menuitems
- var currentTagKey = isNaN(gCurrentViewValue) ? gCurrentViewValue.substr(kViewTagMarker.length) : "";
+ var currentTagKey = gFolderDisplay.view.mailViewIndex == kViewItemTags ?
+ gFolderDisplay.view.mailViewData : "";
var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
.getService(Components.interfaces.nsIMsgTagService);
var tagArray = tagService.getAllTags({});
for (var i = 0; i < tagArray.length; ++i)
{
var tagInfo = tagArray[i];
var menuitem = document.createElement("menuitem");
menuitem.setAttribute("label", tagInfo.tag);
menuitem.setAttribute("value", kViewTagMarker + tagInfo.key);
- if (!aIsMenulist)
- {
- menuitem.setAttribute("type", "radio");
- if (tagInfo.key == currentTagKey)
- menuitem.setAttribute("checked", true);
- }
+ menuitem.setAttribute("type", "radio");
+ if (tagInfo.key == currentTagKey)
+ menuitem.setAttribute("checked", true);
var color = tagInfo.color;
if (color)
menuitem.setAttribute("class", "lc-" + color.substr(1));
aMenupopup.appendChild(menuitem);
}
}
-
function ViewPickerOnLoad()
{
var viewPickerPopup = document.getElementById("viewPickerPopup");
if (viewPickerPopup)
RefreshAllViewPopups(viewPickerPopup, true);
}
--- a/mail/installer/removed-files.in
+++ b/mail/installer/removed-files.in
@@ -1,10 +1,319 @@
+# ****************************************************************************
+# * Generic removals section: this is almost certainly where you want to add *
+# * anything that isn't a connected chunk of files with related and shared *
+# * ifdef conditions. *
+# ****************************************************************************
+chrome/app-chrome.manifest
+chrome/chrome.rdf
+chrome/en-US-mail.jar
+#ifdef XP_WIN
+chrome/en-win.jar
+#endif
+chrome/installed-chrome.txt
+chrome/mail.jar
+chrome/offline.jar
+chrome/offline.manifest
+chrome/overlayinfo/
+chrome/qute.jar
+chrome/US.jar
+#ifdef MOZ_WIDGET_GTK2
+chrome/icons/default/abcardWindow.xpm
+chrome/icons/default/abcardWindow16.xpm
+chrome/icons/default/addressbookWindow.xpm
+chrome/icons/default/addressbookWindow16.xpm
+chrome/icons/default/default.xpm
+chrome/icons/default/messengerWindow.xpm
+chrome/icons/default/messengerWindow16.xpm
+chrome/icons/default/msgcomposeWindow.xpm
+chrome/icons/default/msgcomposeWindow16.xpm
+#endif
+#ifdef XP_MACOSX
+#ifndef ACCESSIBILITY
+components/accessibility.xpt
+#endif
+#endif
+components/accessibility-atk.xpt
+components/airbag.xpt
+components/bookmarks.xpt
+components/downloadmanager.xpt
+components/compreg.dat
+components/history.xpt
+components/jsconsole.xpt
+components/mailnews.xpt
+components/mozgnome.xpt
+components/@DLL_PREFIX@myspell@DLL_SUFFIX@
+components/necko_data.xpt
+components/nsBackgroundUpdateService.js
+components/nsCloseAllWindows.js
+components/nsComposerCmdLineHandler.js
+components/nsDownloadProgressListener.js
+components/nsInterfaceInfoToIDL.js
+components/nsLDAPPrefsService.js
+#ifdef XP_WIN
+#ifndef MOZILLA_1_9_1_BRANCH
+components/nsPostUpdateWin.js
+#endif
+#endif
+components/nsScriptableIO.js
+#ifdef XP_MACOSX
+components/nsSpotlightIntegration.js
+#endif
+components/nsUnsetDefaultMail.js
+components/nsUrlClassifierTable.js
+#ifdef XP_WIN
+components/nsWinSearchIntegration.js
+#endif
+components/progressDlg.xpt
+components/sidebar.xpt
+components/signonviewer.xpt
+components/@DLL_PREFIX@spellchecker@DLL_SUFFIX@
+components/spellchk.dll
+components/@DLL_PREFIX@wallet@DLL_SUFFIX@
+components/wallet.xpt
+components/walleteditor.xpt
+components/walletpreview.xpt
+components/@DLL_PREFIX@walletviewers@DLL_SUFFIX@
+#ifndef MOZ_WEBSERVICES
+components/websrvcs.xpt
+#endif
+#ifdef XP_MACOSX
+components/widget_mac.xpt
+#endif
+components/@DLL_PREFIX@xpcom_compat_c@DLL_SUFFIX@
+components/xpcom_obsolete.xpt
components/xpti.dat
-components/compreg.dat
+components/xptitemp.dat
+components/myspell/en-US.dic
+components/myspell/en-US.aff
+components/myspell/
+defaults/isp/dotmac.rdf
+defaults/isp/movemail.rdf
+defaults/isp/rss.rdf
+defaults/isp/en-US/dotmac.rdf
+defaults/isp/en-US/movemail.rdf
+defaults/isp/en-US/rss.rdf
+defaults/isp/en-US/
+defaults/isp/US/movemail.rdf
+defaults/isp/US/rss.rdf
+defaults/isp/US/
+defaults/isp/
+defaults/messenger/SpamAssassin.sfd
+defaults/messenger/SpamPal.sfd
+defaults/messenger/en-US/mailViews.dat
+defaults/messenger/US/mailViews.dat
+defaults/pref/all.js
+defaults/pref/editor.js
+defaults/pref/inspector.js
+defaults/pref/non-shared.txt
+defaults/pref/security-prefs.js
+defaults/pref/thunderbird.js
+defaults/pref/winpref.js
+defaults/pref/xpinstall.js
+defaults/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf
+defaults/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/
+defaults/profile/extensions/Extensions.rdf
+defaults/profile/extensions/installed-extensions.txt
+defaults/profile/extensions/
+defaults/profile/US/localstore.rdf
+defaults/profile/US/mimeTypes.rdf
+defaults/profile/US/
+#ifdef XP_WIN
+defaults/shortcuts/Mozilla Thunderbird.lnk
+defaults/shortcuts/Mozilla Thunderbird (No Extensions).lnk
+defaults/shortcuts/
+#endif
+defaults/wallet/VcardSchema.tbl
+defaults/wallet/FieldSchema.tbl
+defaults/wallet/SchemaConcat.tbl
+defaults/wallet/DistinguishedSchema.tbl
+defaults/wallet/SchemaStrings.tbl
+defaults/wallet/PositionalSchema.tbl
+defaults/wallet/StateSchema.tbl
+#ifdef MOZ_WIDGET_GTK2
+icons/mozicon128.png
+icons/mozicon16.xpm
+icons/mozicon50.xpm
+#endif
+init.d/README
+isp/gmail.rdf
+modules/JSON.jsm
+plugins/
+res/bloatcycle.html
+res/cmessage.txt
+#ifndef MOZILLA_1_9_1_BRANCH
+res/broken-image.gif
+res/loading-image.gif
+#endif
+res/platform-forms.css
+res/sample.unixpsfonts.properties
+res/builtin/htmlBindings.xml
+res/builtin/platformHTMLBindings.xml
+res/builtin/
+#ifdef XP_MACOSX
+res/cursors/CVS/Entries
+res/cursors/CVS/Repository
+res/cursors/CVS/Root
+res/cursors/CVS/Tag
+#endif
+res/fonts/fontEncoding.properties
+res/fonts/pangoFontEncoding.properties
+res/html/gopher-audio.gif
+res/html/gopher-binary.gif
+res/html/gopher-find.gif
+res/html/gopher-image.gif
+res/html/gopher-menu.gif
+res/html/gopher-movie.gif
+res/html/gopher-sound.gif
+res/html/gopher-telnet.gif
+res/html/gopher-text.gif
+res/html/gopher-unknown.gif
+#ifdef XP_MACOSX
+Thunderbird.app/Contents/Library/Spotlight/Thunderbird.mdimporter/Contents/Info.plist
+Thunderbird.app/Contents/Library/Spotlight/Thunderbird.mdimporter/Contents/MacOS/Thunderbird
+Thunderbird.app/Contents/Library/Spotlight/Thunderbird.mdimporter/Contents/Resources/schema.xml
+Thunderbird.app/Contents/Library/Spotlight/Thunderbird.mdimporter/Contents/Resources/English.lproj/InfoPlist.strings
+Thunderbird.app/Contents/Library/Spotlight/Thunderbird.mdimporter/Contents/Resources/English.lproj/schema.strings
+#endif
+uninstall/UninstallThunderbird.exe
+uninstall/uninst.exe
+uninstall/uninstall.exe
+#ifdef XP_MACOSX
+updater.app/Contents/MacOS/updater.ini
+#endif
+.autoreg
+component.reg
+@DLL_PREFIX@jemalloc@DLL_SUFFIX@
+#ifdef XP_WIN
+#ifdef MOZ_MEMORY
+Microsoft.VC80.CRT.manifest
+msvcm80.dll
+msvcp80.dll
+msvcr80.dll
+#else
+mozcrt19.dll
+#endif
+#endif
+mozilla-installer-bin
+nsldap32v50.dll
+@DLL_PREFIX@ldap50@DLL_SUFFIX@
+nsldappr32v50.dll
+@DLL_PREFIX@prldap50@DLL_SUFFIX@
+redo-prebinding.sh
+regxpcom.exe
+#ifdef XP_WIN
+xpicleanup.exe
+#else
+xpicleanup
+#endif
+@DLL_PREFIX@xpcom_compat@DLL_SUFFIX@
+@DLL_PREFIX@xpistub@DLL_SUFFIX@
+@DLL_PREFIX@zlib@DLL_SUFFIX@
+# ****************************************************************************
+# * End generic removals section. *
+# ****************************************************************************
+
+# ****************************************************************************
+# * Remove Talkback files from old location, where they were in 1.0.x *
+# ****************************************************************************
+components/BrandRes.dll
+components/fullsoft.dll
+components/master.ini
+components/qfaservices.dll
+components/qfaservices.xpt
+components/talkback-l10n.ini
+components/talkback.cnt
+components/talkback.exe
+components/talkback.hlp
+components/libqfaservices.so
+components/talkback/master.ini
+components/talkback/talkback
+components/talkback/talkback.so
+components/talkback/XTalkback.ad
+# **************************************************************************##
+# * Remove Talkback files from new location, where they were in 1.5.x and *
+# * 2.0.x. *
+# ****************************************************************************
+extensions/talkback@mozilla.org/
+extensions/talkback@mozilla.org/install.rdf
+extensions/talkback@mozilla.org/chrome.manifest
+extensions/talkback@mozilla.org/components/qfaservices.xpt
+extensions/talkback@mozilla.org/components/@DLL_PREFIX@qfaservices@DLL_SUFFIX@
+#ifdef XP_WIN
+extensions/talkback@mozilla.org/components/BrandRes.dll
+extensions/talkback@mozilla.org/components/fullsoft.dll
+extensions/talkback@mozilla.org/components/master.ini
+extensions/talkback@mozilla.org/components/talkback-l10n.ini
+extensions/talkback@mozilla.org/components/talkback.cnt
+extensions/talkback@mozilla.org/components/talkback.exe
+extensions/talkback@mozilla.org/components/talkback.hlp
+extensions/talkback@mozilla.org/InstallDisabled
+#else
+#ifdef XP_MACOSX
+extensions/talkback@mozilla.org/components/talkback/master.ini
+extensions/talkback@mozilla.org/components/talkback/talkback@DLL_SUFFIX@
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Info.plist
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/MacOS/Talkback
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/pbdevelopment.plist
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/PkgInfo
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/delete.tiff
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/disable.tiff
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/enable.tiff
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/ArchivingSettings.nib/classes.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/ArchivingSettings.nib/info.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/ArchivingSettings.nib/objects.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/InfoPlist.strings
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/IntroWizard.nib/classes.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/IntroWizard.nib/info.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/IntroWizard.nib/objects.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/Localizable.strings
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/MainMenu.nib/info.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/MainMenu.nib/objects.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/ProxySettings.nib/classes.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/ProxySettings.nib/info.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/ProxySettings.nib/objects.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/SendingSettings.nib/classes.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/SendingSettings.nib/info.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/SendingSettings.nib/objects.nib
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/KeyInfoKeys.plist
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/KeyInfoSections.plist
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/send.tiff
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/sort_ascending.tiff
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/sort_descending.tiff
+extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/Talkback.icns
+#else
+extensions/talkback@mozilla.org/components/talkback/talkback
+extensions/talkback@mozilla.org/components/talkback/XTalkback.ad
+extensions/talkback@mozilla.org/components/talkback/master.ini
+extensions/talkback@mozilla.org/components/talkback/talkback.so
+#endif
+#endif
+# ****************************************************************************
+# * End removing Talkback. *
+# ****************************************************************************
+
+# ****************************************************************************
+# * Remove pre-extension PalmSync files. *
+# ****************************************************************************
+HSAPI.dll
+CondMgr.dll
+mozABConduit.dll
+components/palmsync.dll
+components/palmSync.xpt
+# ****************************************************************************
+# * End pre-extensions PalmSync files. *
+# ****************************************************************************
+
+# ****************************************************************************
+# * Remove the DLLs that were present at the time (2004-05) when we started *
+# * shipping static builds on Windows. You almost certainly don't want to *
+# * add anything to this section. *
+# ****************************************************************************
components/accessibility.dll
components/appcomps.dll
components/appshell.dll
components/caps.dll
components/chrome.dll
components/composer.dll
components/docshell.dll
components/editor.dll
@@ -12,55 +321,62 @@ components/embedcomponents.dll
components/gkgfxwin.dll
components/gklayout.dll
components/gkparser.dll
components/gkwidget.dll
components/i18n.dll
components/imgicon.dll
components/imglib2.dll
components/import.dll
+components/jsdom.dll
components/mail.dll
components/mork.dll
components/mozfind.dll
components/mozldap.dll
components/msgMapi.dll
components/msgsmime.dll
-components/myspell.dll
components/necko.dll
components/necko2.dll
components/nsprefm.dll
components/pipboot.dll
components/pipnss.dll
components/pippki.dll
components/profile.dll
components/profilemigration.dll
components/rdf.dll
-components/spellchk.dll
components/txmgr.dll
components/uconv.dll
-components/@DLL_PREFIX@wallet@DLL_SUFFIX@
-components/@DLL_PREFIX@walletviewers@DLL_SUFFIX@
components/webbrwsr.dll
components/wlltvwrs.dll
components/xmlextras.dll
+components/xmlextras.xpt
components/xpc3250.dll
components/xppref32.dll
-nsldap32v50.dll
-nsldappr32v50.dll
-components/wallet.xpt
-components/walleteditor.xpt
-components/walletpreview.xpt
+gkgfx.dll
+mozz.dll
+# ****************************************************************************
+# * End removing old shared dlls. *
+# ****************************************************************************
+
+
+# ****************************************************************************
+# * Remove the XPTs that were present at the time (2004-09) when we started *
+# * linking XPTs on Windows. You almost certainly don't want to add anything *
+# * to this section. *
+# ****************************************************************************
#ifdef XP_WIN
components/accessibility-msaa.xpt
components/accessibility.xpt
components/addrbook.xpt
components/alerts.xpt
components/appshell.xpt
+components/autocomplete.xpt
components/caps.xpt
components/chardet.xpt
+components/chrome.xpt
components/commandhandler.xpt
components/composer.xpt
components/content_base.xpt
components/content_html.xpt
components/content_htmldoc.xpt
components/content_xmldoc.xpt
components/content_xslt.xpt
components/crashreporter.xpt
@@ -68,28 +384,25 @@ components/docshell_base.xpt
components/dom.xpt
components/dom_base.xpt
components/dom_core.xpt
components/dom_css.xpt
components/dom_events.xpt
components/dom_html.xpt
components/dom_offline.xpt
components/dom_range.xpt
-#ifdef MOZILLA_1_9_1_BRANCH
-components/dom_smil.xpt
-#endif
components/dom_stylesheets.xpt
components/dom_traversal.xpt
components/dom_views.xpt
components/dom_xbl.xpt
components/dom_xpath.xpt
components/dom_xul.xpt
-components/downloadmanager.xpt
components/editor.xpt
components/embed_base.xpt
+components/extensions.xpt
components/exthandler.xpt
components/find.xpt
components/gfx.xpt
components/helperAppDlg.xpt
components/htmlparser.xpt
components/imgicon.xpt
components/imglib2.xpt
components/impComm4xMail.xpt
@@ -99,96 +412,100 @@ components/jar.xpt
components/jsdservice.xpt
components/jsurl.xpt
components/layout_base.xpt
components/layout_printing.xpt
components/layout_xul.xpt
components/layout_xul_tree.xpt
components/locale.xpt
components/lwbrk.xpt
-components/mailnews.xpt
components/mailview.xpt
components/mapihook.xpt
components/mime.xpt
components/mimetype.xpt
components/mozbrwsr.xpt
components/mozfind.xpt
components/mozldap.xpt
components/msgbase.xpt
components/msgcompo.xpt
components/msgdb.xpt
components/msgimap.xpt
components/msglocal.xpt
components/msgnews.xpt
components/msgsearch.xpt
components/msgsmime.xpt
components/necko.xpt
+components/necko_about.xpt
components/necko_cache.xpt
components/necko_cookie.xpt
-components/necko_data.xpt
components/necko_dns.xpt
components/necko_file.xpt
+components/necko_ftp.xpt
components/necko_http.xpt
components/necko_jar.xpt
components/necko_res.xpt
components/necko_strconv.xpt
components/pipboot.xpt
components/pipnss.xpt
components/pippki.xpt
components/pref.xpt
components/prefmigr.xpt
components/profile.xpt
-components/progressDlg.xpt
components/proxyObject.xpt
components/rdf.xpt
-components/signonviewer.xpt
components/spellchecker.xpt
+components/toolkitprofile.xpt
components/txmgr.xpt
components/txtsvc.xpt
components/uconv.xpt
+components/ucnative.xpt
components/unicharutil.xpt
+components/update.xpt
components/uriloader.xpt
+components/util.xpt
components/webbrowserpersist.xpt
components/webBrowser_core.xpt
components/webshell_idls.xpt
components/widget.xpt
components/windowds.xpt
components/windowwatcher.xpt
components/winhooks.xpt
components/xmlextras.xpt
components/xpcom_base.xpt
components/xpcom_components.xpt
components/xpcom_ds.xpt
components/xpcom_io.xpt
-components/xpcom_obsolete.xpt
components/xpcom_thread.xpt
components/xpcom_xpti.xpt
components/xpcom.xpt
components/xpconnect.xpt
components/xpinstall.xpt
components/xuldoc.xpt
components/xultmpl.xpt
-components/downloadmanager.xpt
-#ifndef MOZILLA_1_9_1_BRANCH
-components/nsPostUpdateWin.js
#endif
-#endif
+# ****************************************************************************
+# * End of XPTs at the time we started linking them on Windows. *
+# ****************************************************************************
+
+# ****************************************************************************
+# * Remove the XPTs that were present at the time (2009-04) when we started *
+# * linking XPTs on Linux. You almost certainly don't want to add anything *
+# * to this section. *
+# ****************************************************************************
#ifndef MOZILLA_1_9_1_BRANCH
#ifdef XP_UNIX
#ifndef XP_MACOSX
-components/accessibility-atk.xpt
components/accessibility.xpt
components/activity.xpt
components/addrbook.xpt
components/alerts.xpt
components/appshell.xpt
components/appstartup.xpt
components/autocomplete.xpt
components/autoconfig.xpt
-components/bookmarks.xpt
components/caps.xpt
components/chardet.xpt
components/chrome.xpt
components/commandhandler.xpt
components/commandlines.xpt
components/composer.xpt
components/content_base.xpt
components/content_htmldoc.xpt
@@ -228,55 +545,50 @@ components/embed_base.xpt
components/extensions.xpt
components/exthandler.xpt
components/exthelper.xpt
components/fastfind.xpt
components/feeds.xpt
components/filepicker.xpt
components/find.xpt
components/gfx.xpt
-components/history.xpt
components/htmlparser.xpt
components/imgicon.xpt
components/imglib2.xpt
components/impComm4xMail.xpt
components/import.xpt
components/inspector.xpt
components/intl.xpt
components/jar.xpt
-components/jsconsole.xpt
components/jsdservice.xpt
components/layout_base.xpt
components/layout_printing.xpt
components/layout_xul_tree.xpt
components/layout_xul.xpt
components/locale.xpt
components/loginmgr.xpt
components/lwbrk.xpt
-components/mailnews.xpt
components/mailprofilemigration.xpt
components/mailview.xpt
components/mimetype.xpt
components/mime.xpt
components/mozbrwsr.xpt
components/mozfind.xpt
-components/mozgnome.xpt
components/mozldap.xpt
components/msgbase.xpt
components/msgcompose.xpt
components/msgdb.xpt
components/msgimap.xpt
components/msglocal.xpt
components/msgnews.xpt
components/msgsearch.xpt
components/msgsmime.xpt
components/necko_about.xpt
components/necko_cache.xpt
components/necko_cookie.xpt
-components/necko_data.xpt
components/necko_dns.xpt
components/necko_file.xpt
components/necko_ftp.xpt
components/necko_http.xpt
components/necko_res.xpt
components/necko_socket.xpt
components/necko_strconv.xpt
components/necko_viewsource.xpt
@@ -285,23 +597,21 @@ components/parentalcontrols.xpt
components/pipboot.xpt
components/pipnss.xpt
components/pippki.xpt
components/places.xpt
components/plugin.xpt
components/prefetch.xpt
components/pref.xpt
components/profile.xpt
-components/progressDlg.xpt
components/proxyObjInst.xpt
components/rdf.xpt
components/saxparser.xpt
components/shellservice.xpt
components/shistory.xpt
-components/signonviewer.xpt
components/spellchecker.xpt
components/storage.xpt
components/toolkitprofile.xpt
components/toolkitremote.xpt
components/txmgr.xpt
components/txtsvc.xpt
components/uconv.xpt
components/unicharutil.xpt
@@ -316,210 +626,23 @@ components/websrvcs.xpt
components/widget.xpt
components/windowds.xpt
components/windowwatcher.xpt
components/xpautocomplete.xpt
components/xpcom_base.xpt
components/xpcom_components.xpt
components/xpcom_ds.xpt
components/xpcom_io.xpt
-components/xpcom_obsolete.xpt
components/xpcom_system.xpt
components/xpcom_threads.xpt
components/xpcom_xpti.xpt
components/xpconnect.xpt
components/xpinstall.xpt
components/xulapp.xpt
components/xuldoc.xpt
components/xultmpl.xpt
components/zipwriter.xpt
#endif
#endif
#endif
-components/nsUnsetDefaultMail.js
-components/nsBackgroundUpdateService.js
-components/nsCloseAllWindows.js
-components/nsDownloadProgressListener.js
-components/nsLDAPPrefsService.js
-components/xptitemp.dat
-component.reg
-chrome/installed-chrome.txt
-chrome/chrome.rdf
-chrome/app-chrome.manifest
-chrome/mail.jar
-chrome/qute.jar
-chrome/offline.jar
-chrome/offline.manifest
-chrome/en-US-mail.jar
-chrome/overlayinfo/
-#ifdef MOZ_WIDGET_GTK2
-chrome/icons/default/abcardWindow.xpm
-chrome/icons/default/abcardWindow16.xpm
-chrome/icons/default/addressbookWindow.xpm
-chrome/icons/default/addressbookWindow16.xpm
-chrome/icons/default/msgcomposeWindow.xpm
-chrome/icons/default/msgcomposeWindow16.xpm
-chrome/icons/default/messengerWindow.xpm
-chrome/icons/default/messengerWindow16.xpm
-chrome/icons/default/default.xpm
-icons/mozicon128.png
-icons/mozicon16.xpm
-icons/mozicon50.xpm
-#endif
-defaults/pref/all.js
-defaults/pref/security-prefs.js
-defaults/pref/winpref.js
-defaults/pref/xpinstall.js
-defaults/pref/thunderbird.js
-defaults/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf
-defaults/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/
-defaults/profile/extensions/Extensions.rdf
-defaults/profile/extensions/installed-extensions.txt
-defaults/profile/extensions/
-defaults/profile/US/localstore.rdf
-defaults/profile/US/mimeTypes.rdf
-defaults/profile/US/
-defaults/messenger/SpamAssassin.sfd
-defaults/messenger/SpamPal.sfd
-defaults/isp/rss.rdf
-defaults/isp/movemail.rdf
-defaults/isp/dotmac.rdf
-defaults/isp/en-US/rss.rdf
-defaults/isp/en-US/movemail.rdf
-defaults/isp/en-US/dotmac.rdf
-defaults/isp/en-US/
-defaults/isp/
-defaults/wallet/VcardSchema.tbl
-defaults/wallet/FieldSchema.tbl
-defaults/wallet/SchemaConcat.tbl
-defaults/wallet/DistinguishedSchema.tbl
-defaults/wallet/SchemaStrings.tbl
-defaults/wallet/PositionalSchema.tbl
-defaults/wallet/StateSchema.tbl
-isp/gmail.rdf
-
-components/@DLL_PREFIX@xpcom_compat_c@DLL_SUFFIX@
-@DLL_PREFIX@xpcom_compat@DLL_SUFFIX@
-@DLL_PREFIX@zlib@DLL_SUFFIX@
-@DLL_PREFIX@xpistub@DLL_SUFFIX@
-#Remove Talkback files from old location (in case user updates from 1.0.x)
-components/BrandRes.dll
-components/fullsoft.dll
-components/master.ini
-components/qfaservices.dll
-components/qfaservices.xpt
-components/talkback-l10n.ini
-components/talkback.cnt
-components/talkback.exe
-components/talkback.hlp
-components/libqfaservices.so
-components/talkback/master.ini
-components/talkback/talkback
-components/talkback/talkback.so
-components/talkback/XTalkback.ad
-HSAPI.dll
-CondMgr.dll
-mozABConduit.dll
-components/palmsync.dll
-components/palmSync.xpt
-uninstall/UninstallThunderbird.exe
-uninstall/uninst.exe
-uninstall/uninstall.exe
-components/myspell/en-US.dic
-components/myspell/en-US.aff
-regxpcom.exe
-components/xmlextras.xpt
-res/cmessage.txt
-#ifndef MOZILLA_1_9_1_BRANCH
-res/broken-image.gif
-res/loading-image.gif
-#endif
-res/html/gopher-audio.gif
-res/html/gopher-binary.gif
-res/html/gopher-find.gif
-res/html/gopher-image.gif
-res/html/gopher-menu.gif
-res/html/gopher-movie.gif
-res/html/gopher-sound.gif
-res/html/gopher-telnet.gif
-res/html/gopher-text.gif
-res/html/gopher-unknown.gif
-extensions/talkback@mozilla.org/
-extensions/talkback@mozilla.org/install.rdf
-extensions/talkback@mozilla.org/chrome.manifest
-extensions/talkback@mozilla.org/components/qfaservices.xpt
-extensions/talkback@mozilla.org/components/@DLL_PREFIX@qfaservices@DLL_SUFFIX@
-#ifdef XP_WIN
-extensions/talkback@mozilla.org/components/BrandRes.dll
-extensions/talkback@mozilla.org/components/fullsoft.dll
-extensions/talkback@mozilla.org/components/master.ini
-extensions/talkback@mozilla.org/components/talkback-l10n.ini
-extensions/talkback@mozilla.org/components/talkback.cnt
-extensions/talkback@mozilla.org/components/talkback.exe
-extensions/talkback@mozilla.org/components/talkback.hlp
-extensions/talkback@mozilla.org/InstallDisabled
-#else
-#ifdef XP_MACOSX
-extensions/talkback@mozilla.org/components/talkback/master.ini
-extensions/talkback@mozilla.org/components/talkback/talkback@DLL_SUFFIX@
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Info.plist
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/MacOS/Talkback
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/pbdevelopment.plist
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/PkgInfo
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/delete.tiff
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/disable.tiff
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/enable.tiff
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/ArchivingSettings.nib/classes.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/ArchivingSettings.nib/info.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/ArchivingSettings.nib/objects.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/InfoPlist.strings
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/IntroWizard.nib/classes.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/IntroWizard.nib/info.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/IntroWizard.nib/objects.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/Localizable.strings
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/MainMenu.nib/info.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/MainMenu.nib/objects.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/ProxySettings.nib/classes.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/ProxySettings.nib/info.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/ProxySettings.nib/objects.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/SendingSettings.nib/classes.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/SendingSettings.nib/info.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/English.lproj/SendingSettings.nib/objects.nib
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/KeyInfoKeys.plist
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/KeyInfoSections.plist
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/send.tiff
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/sort_ascending.tiff
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/sort_descending.tiff
-extensions/talkback@mozilla.org/components/talkback/Talkback.app/Contents/Resources/Talkback.icns
-updater.app/Contents/MacOS/updater.ini
-#else
-extensions/talkback@mozilla.org/components/talkback/talkback
-extensions/talkback@mozilla.org/components/talkback/XTalkback.ad
-extensions/talkback@mozilla.org/components/master.ini
-extensions/talkback@mozilla.org/components/talkback.so
-#endif
-#endif
-components/airbag.xpt
-components/nsUrlClassifierTable.js
-components/nsScriptableIO.js
-#ifdef XP_WIN
-#ifdef MOZ_MEMORY
-Microsoft.VC80.CRT.manifest
-msvcm80.dll
-msvcp80.dll
-msvcr80.dll
-#else
-mozcrt19.dll
-#endif
-#endif
-#ifdef XP_WIN
-xpicleanup.exe
-#else
-xpicleanup
-#endif
-#ifdef XP_WIN
-components/nsWinSearchIntegration.js
-#else
-#ifdef XP_MACOSX
-components/nsSpotlightIntegration.js
-#endif
-#endif
+# ****************************************************************************
+# * End of XPTs at the time we started linking them on Linux. *
+# ****************************************************************************
--- a/mail/installer/windows/packages-static
+++ b/mail/installer/windows/packages-static
@@ -63,16 +63,17 @@ bin\defaults\profile\prefs.js
bin\defaults\profile\mimeTypes.rdf
bin\isp\*
bin\components\aboutRights.js
bin\components\activity.xpt
bin\components\addrbook.xpt
bin\components\mime.xpt
+bin\components\steel.xpt
bin\components\msgbase.xpt
bin\components\msgcompo.xpt
bin\components\msgdb.xpt
bin\components\msgimap.xpt
bin\components\msglocal.xpt
bin\components\msgnews.xpt
bin\components\msgsearch.xpt
bin\components\import.xpt
@@ -114,16 +115,17 @@ bin\components\jsmimeemitter.js
; Mail Extensions (smime, etc.)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
bin\MapiProxy.dll
bin\mozMapi32.dll
bin\components\mapihook.xpt
bin\components\nsSetDefaultMail.js
bin\components\offlineStartup.js
bin\components\nsMailDefaultHandler.js
+bin\components\steelApplication.js
bin\components\mdn-service.js
bin\components\smime-service.js
bin\components\msgsmime.xpt
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
--- a/mail/locales/en-US/chrome/messenger/aboutRights.dtd
+++ b/mail/locales/en-US/chrome/messenger/aboutRights.dtd
@@ -1,12 +1,12 @@
<!-- rights.locale-direction instead of the usual local.dir entity, so RTL can skip translating page. -->
<!ENTITY rights.locale-direction "ltr">
-<!ENTITY rights.pagetitle "about:rights">
-<!ENTITY rights.intro-header "About Your Rights">
+<!ENTITY rights.title "About Your Rights">
+<!ENTITY rights.intro-header "About Your Rights">
<!ENTITY rights.intro "&brandFullName; is free and open source software, built by a community of thousands from all over the world. There are a few things you should know:">
<!-- Note on pointa / pointb / pointc form:
These points each have an embedded link in the HTML, so each point is
split into chunks for text before the link, the link text, and the text
after the link. If a localized grammar doesn't need the before or after
chunk, it can be left blank.
--- a/mail/locales/en-US/chrome/messenger/am-offline.dtd
+++ b/mail/locales/en-US/chrome/messenger/am-offline.dtd
@@ -13,22 +13,22 @@
<!ENTITY nntpDownloadMsg.accesskey "e">
<!ENTITY retentionCleanup.label "To recover disk space, old messages can be permanently deleted.">
<!ENTITY retentionCleanupImap.label "To recover disk space, old messages can be permanently deleted, both local copies and originals on the remote server.">
<!ENTITY retentionCleanupPop.label "To recover disk space, old messages can be permanently deleted, including originals on the remote server.">
<!ENTITY retentionKeepMsg.label "Delete messages more than">
<!ENTITY retentionKeepMsg.accesskey "t">
<!ENTITY retentionKeepAll.label "Don't delete any messages">
<!ENTITY retentionKeepAll.accesskey "n">
-<!ENTITY retentionKeepNew.label "Delete all but the last">
-<!ENTITY retentionKeepNew.accesskey "b">
-<!ENTITY retentionKeepUnread.label "Always delete read messages">
-<!ENTITY retentionKeepUnread.accesskey "w">
+<!ENTITY retentionKeepRecent.label "Delete all but the most recent">
+<!ENTITY retentionKeepRecent.accesskey "b">
+<!-- LOCALIZATION NOTE: Unhide with .keepUnreadOnly { display: -moz-box; } -->
+<!ENTITY retentionKeepUnreadHidden.label "Always delete read messages (overrides age settings)">
<!ENTITY retentionApplyToFlagged.label "Always keep starred messages">
<!ENTITY retentionApplyToFlagged.accesskey "k">
-<!ENTITY nntpRemoveBody.label "Only message bodies less than">
-<!ENTITY nntpRemoveBody.accesskey "O">
+<!ENTITY nntpRemoveMsgBody.label "Remove bodies from messages more than">
+<!ENTITY nntpRemoveMsgBody.accesskey "o">
<!ENTITY offlineSelectNntp.label "Select newsgroups for offline use…">
<!ENTITY offlineSelectNntp.accesskey "S">
<!ENTITY offlineImapAdvancedOffline.label "Advanced…">
<!ENTITY offlineImapAdvancedOffline.accesskey "v">
<!ENTITY syncGroupTitle.label "Message Synchronizing">
<!ENTITY diskspaceGroupTitle.label "Disk Space">
--- a/mail/locales/en-US/chrome/messenger/folderProps.dtd
+++ b/mail/locales/en-US/chrome/messenger/folderProps.dtd
@@ -56,20 +56,20 @@
<!ENTITY message.label "messages">
<!ENTITY retentionCleanup.label "To recover disk space, old messages can be permanently deleted.">
<!ENTITY retentionCleanupImap.label "To recover disk space, old messages can be permanently deleted, both local copies and originals on the remote server.">
<!ENTITY retentionCleanupPop.label "To recover disk space, old messages can be permanently deleted, including originals on the remote server.">
<!ENTITY retentionDeleteMsg.label "Delete messages more than">
<!ENTITY retentionDeleteMsg.accesskey "m">
<!ENTITY retentionKeepAll.label "Don't delete any messages">
<!ENTITY retentionKeepAll.accesskey "A">
-<!ENTITY retentionKeepNew.label "Delete all but the last">
-<!ENTITY retentionKeepNew.accesskey "l">
-<!ENTITY retentionKeepUnread.label "Always delete read messages">
-<!ENTITY retentionKeepUnread.accesskey "r">
+<!ENTITY retentionKeepRecent.label "Delete all but the most recent">
+<!ENTITY retentionKeepRecent.accesskey "l">
+<!-- LOCALIZATION NOTE: Unhide with .keepUnreadOnly { display: -moz-box; } -->
+<!ENTITY retentionKeepUnreadHidden.label "Always delete read messages (overrides age settings)">
<!ENTITY retentionApplyToFlagged.label "Always keep starred messages">
<!ENTITY retentionApplyToFlagged.accesskey "e">
<!ENTITY folderSynchronizationTab.label "Synchronization">
<!ENTITY folderCheckForNewMessages.label "Check this folder for new messages">
<!ENTITY folderCheckForNewMessages.accesskey "C">
<!ENTITY offlineFolder.check.label "Select this folder for offline use">
--- a/mail/locales/en-US/chrome/messenger/messengercompose/composeMsgs.properties
+++ b/mail/locales/en-US/chrome/messenger/messengercompose/composeMsgs.properties
@@ -328,14 +328,14 @@ mailnews.reply_header_originalmessage=--
## Strings used by the rename attachment dialog
renameAttachmentTitle=Rename Attachment
renameAttachmentMessage=New attachment name:
## Attachment Reminder
## LOCALIZATION NOTE (mail.compose.attachment_reminder_keywords): comma separated
## words that that should trigger an attachment reminder.
-mail.compose.attachment_reminder_keywords=attachment,attached,.doc,.pdf,resume,cover letter
+mail.compose.attachment_reminder_keywords=.doc,.pdf,attachment,attach,attached,attaching,enclosed,CV,cover letter
attachmentReminderTitle=Attachment Reminder
attachmentReminderMsg=Did you forget to add an attachment?
attachmentReminderYesIForgot=Oh, I did!
attachmentReminderFalseAlarm=No, Send Now
--- a/mail/locales/en-US/chrome/messenger/msgHdrViewOverlay.dtd
+++ b/mail/locales/en-US/chrome/messenger/msgHdrViewOverlay.dtd
@@ -66,19 +66,16 @@
<!ENTITY trashButton.tooltiptext "delete">
<!ENTITY otherActionsButton.label "other actions">
<!ENTITY saveAsMenuItem.label "save as…">
<!ENTITY saveAsMenuItem.accesskey "S">
<!ENTITY viewSourceMenuItem.label "view source…">
<!ENTITY viewSourceMenuItem.accesskey "V">
-<!ENTITY hideDetailsButton.label "hide details">
-<!ENTITY showDetailsButton.label "show details">
-
<!ENTITY openAttachmentCmd.label "Open">
<!ENTITY openAttachmentCmd.accesskey "O">
<!ENTITY saveAsAttachmentCmd.label "Save As…">
<!ENTITY saveAsAttachmentCmd.accesskey "A">
<!ENTITY detachAttachmentCmd.label "Detach…">
<!ENTITY deleteAttachmentCmd.label "Delete">
<!ENTITY saveAllAttachmentsCmd.label "Save All…">
<!ENTITY saveAllAttachmentsCmd.accesskey "S">
new file mode 100644
--- /dev/null
+++ b/mail/steel/Makefile.in
@@ -0,0 +1,52 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is FUEL.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2006
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Mark Finkle <mfinkle@mozilla.com>
+# John Resig <jresig@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH = ../..
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = steel
+XPIDL_MODULE = steel
+
+XPIDLSRCS = steelIApplication.idl
+
+EXTRA_PP_COMPONENTS = steelApplication.js
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/mail/steel/steelApplication.js
@@ -0,0 +1,93 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is STEEL code.
+ *
+ * The Initial Developer of the Original Code is
+ * Joey Minta <jminta@gmail.com>
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+//=================================================
+// Factory - Treat Application as a singleton
+// XXX This is required, because we're registered for the 'JavaScript global
+// privileged property' category, whose handler always calls createInstance.
+// See bug 386535.
+var gSingleton = null;
+var ApplicationFactory = {
+ createInstance: function af_ci(aOuter, aIID) {
+ if (aOuter != null)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+
+ if (gSingleton == null) {
+ gSingleton = new Application();
+ }
+
+ return gSingleton.QueryInterface(aIID);
+ }
+};
+
+function Application() {
+ this.initToolkitHelpers();
+}
+
+Application.prototype = {
+ classDescription: "Application",
+ classID: Components.ID("f265021a-7f1d-4b4b-bdc6-9aedca4d8f13"),
+ contractID: "@mozilla.org/steel/application;1",
+
+ // redefine the default factory for XPCOMUtils
+ _xpcom_factory: ApplicationFactory,
+
+ // for nsISupports
+ QueryInterface : XPCOMUtils.generateQI([Ci.steelIApplication, Ci.extIApplication, Ci.nsIObserver, Ci.nsIClassInfo]),
+
+ getInterfaces : function app_gi(aCount) {
+ let interfaces = [Ci.steelIApplication, Ci.extIApplication, Ci.nsIObserver, Ci.nsIClassInfo];
+ aCount.value = interfaces.length;
+ return interfaces;
+ }
+
+};
+
+//module initialization
+function NSGetModule(aCompMgr, aFileSpec) {
+ // set the proto
+ Application.prototype.__proto__ = extApplication.prototype;
+
+ // now we can finally return our module
+ return XPCOMUtils.generateModule([Application]);
+}
+
+#include ../../mozilla/toolkit/components/exthelper/extApplication.js
new file mode 100644
--- /dev/null
+++ b/mail/steel/steelIApplication.idl
@@ -0,0 +1,45 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is STEEL code.
+ *
+ * The Initial Developer of the Original Code is
+ * Joey Minta <jminta@gmail.com>
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "extIApplication.idl"
+
+/**
+ * The core STEEL object, available in the global scope
+ */
+[scriptable, uuid(f265021a-7f1d-4b4b-bdc6-9aedca4d8f13)]
+interface steelIApplication : extIApplication {
+ //xxx place-holder for now
+};
--- a/mail/test/mozmill/content-policy/test-msg-content-policy.js
+++ b/mail/test/mozmill/content-policy/test-msg-content-policy.js
@@ -107,18 +107,18 @@ const jsMsgBody = '<!DOCTYPE HTML PUBLIC
'</big></big></big>\n' +
'<script language="javascript"/>\n'+
'var jsIsTurnedOn = true;\n' +
'</script>\n' +
'\n' +
'</body>\n' +
'</html>\n';
-const Cc = Components.classes;
-const Ci = Components.interfaces;
+var Cc = Components.classes;
+var Ci = Components.interfaces;
const kTestFolderName = "testFolder";
let am = Cc["@mozilla.org/messenger/account-manager;1"].
getService(Ci.nsIMsgAccountManager);
let localRootFolder = am.localFoldersServer.rootFolder;
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/folder-display/test-deletion-with-multiple-displays.js
@@ -0,0 +1,196 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test that deleting a message in a given tab or window properly updates both
+ * that tab/window as well as all other tabs/windows. We also test that the
+ * message tab title updates appropriately through all of this.
+ */
+var MODULE_NAME = 'test-deletion-with-multiple-displays';
+
+var RELATIVE_ROOT = '../shared-modules';
+var MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers'];
+
+var folder;
+
+function setupModule(module) {
+ let fdh = collector.getModule('folder-display-helpers');
+ fdh.installInto(module);
+ let wh = collector.getModule('window-helpers');
+ wh.installInto(module);
+
+ folder = create_folder("DeletionA");
+ // we want exactly as many messages as we plan to delete, so that we can test
+ // that the message window and tabs close when they run out of things to
+ // to display.
+ make_new_sets_in_folder(folder, [{count: 4}]);
+}
+
+
+var tabFolder, tabMessage, curMessage, nextMessage;
+
+/**
+ * The message window controller. Short names because controllers get used a
+ * lot.
+ */
+var msgc;
+
+/**
+ * Have a message displayed in a folder tab, message tab, and message window.
+ * The idea is that as we delete from the various sources, they should all
+ * advance in lock-step through their messages, simplifying our lives (but
+ * making us explode forevermore the first time any of the tests fail.)
+ */
+function test_open_message_in_all_three_display_mechanisms() {
+ // - Select the message in this tab.
+ tabFolder = be_in_folder(folder);
+ curMessage = select_click_row(0);
+ assert_selected_and_displayed(curMessage);
+
+ // - Open the tab with the message
+ tabMessage = open_selected_message_in_new_tab();
+ assert_selected_and_displayed(curMessage);
+ assert_tab_titled_from(tabMessage, curMessage);
+
+ // - Open the window with the message
+ // need to go back to the folder tab. (well, should.)
+ switch_tab(tabFolder);
+ msgc = open_selected_message_in_new_window();
+ assert_selected_and_displayed(msgc, curMessage);
+}
+
+/**
+ * Perform a deletion from the folder tab, verify the others update correctly
+ * (advancing to the next message).
+ */
+function test_delete_in_folder_tab() {
+ // - plan to end up on the guy who is currently at index 1
+ curMessage = mc.dbView.getMsgHdrAt(1);
+ // while we're at it, figure out who is at 2 for the next step
+ nextMessage = mc.dbView.getMsgHdrAt(2);
+ // - delete the message
+ press_delete();
+ // - make sure the right guy is selected, and that he is at index 0
+ assert_selected_and_displayed(curMessage);
+ assert_selected_and_displayed(0);
+
+ // - make sure the message tab updated its title even without us switching
+ assert_tab_titled_from(tabMessage, curMessage);
+
+ // - switch to the message tab, make sure he is now on the right guy
+ switch_tab(tabMessage);
+ assert_selected_and_displayed(curMessage);
+
+ // - check the window
+ assert_selected_and_displayed(msgc, curMessage);
+}
+
+/**
+ * Perform a deletion from the message tab, verify the others update correctly
+ * (advancing to the next message).
+ */
+function test_delete_in_message_tab() {
+ // (we're still on the message tab, and nextMessage is the guy we want to see
+ // once the delete completes.)
+ press_delete();
+ curMessage = nextMessage;
+ assert_selected_and_displayed(curMessage);
+ assert_tab_titled_from(tabMessage, curMessage);
+
+ // - switch to the folder tab and make sure he is on the right guy and at 0
+ switch_tab(tabFolder);
+ assert_selected_and_displayed(curMessage);
+ assert_selected_and_displayed(0);
+ // figure out the next guy...
+ nextMessage = mc.dbView.getMsgHdrAt(1);
+ if (!nextMessage)
+ throw new Error("We ran out of messages early?");
+
+ // - check the message window
+ assert_selected_and_displayed(msgc, curMessage);
+}
+
+/**
+ * Perform a deletion from the message window, verify the others update
+ * correctly (advancing to the next message).
+ */
+function test_delete_in_message_window() {
+ // - delete, verify in the message window
+ press_delete(msgc);
+ curMessage = nextMessage;
+ assert_selected_and_displayed(msgc, curMessage);
+
+ // - verify in the folder tab (we're still on this tab)
+ assert_selected_and_displayed(curMessage);
+ assert_selected_and_displayed(0);
+
+ // - verify in the message tab
+ switch_tab(tabMessage);
+ assert_selected_and_displayed(curMessage);
+ assert_tab_titled_from(tabMessage, curMessage);
+}
+
+/**
+ * Delete the last message in that folder, which should close all message
+ * displays. For comprehensiveness, first open an additional message tab
+ * of the message so that we can test foreground and background closing at the
+ * same time.
+ */
+function test_delete_last_message_closes_message_displays() {
+ // - open the additional message tab
+ switch_tab(tabFolder);
+ let tabMessage2 = open_selected_message_in_new_tab();
+
+ // - prep for the message window disappearing
+ plan_for_window_close(msgc);
+
+ // - let's arbitrarily perform the deletion on this message tab
+ press_delete();
+
+ // - the message window should have gone away...
+ // (this also helps ensure that the 3pane gets enough event loop time to do
+ // all that it needs to accomplish)
+ wait_for_window_close(msgc);
+ msgc = null;
+
+ // - and we should now be on the folder tab and there should be no other tabs
+ if (mc.tabmail.tabInfo.length != 1)
+ throw new Error("There should only be one tab left!");
+ // the below check is implied by the previous check if things are sane-ish
+ if (mc.tabmail.currentTabInfo != tabFolder)
+ throw new Error("We should be on the folder tab!");
+}
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/folder-display/test-mail-views.js
@@ -0,0 +1,112 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var MODULE_NAME = 'test-mail-views';
+
+var RELATIVE_ROOT = '../shared-modules';
+var MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers'];
+
+var baseFolder, savedFolder;
+var setUntagged, setTagged;
+
+Components.utils.import("resource://app/modules/mailViewManager.js");
+
+var setupModule = function(module) {
+ let fdh = collector.getModule('folder-display-helpers');
+ fdh.installInto(module);
+ let wh = collector.getModule('window-helpers');
+ wh.installInto(module);
+
+ // Create a folder with some messages that have no tags and some that are
+ // tagged Important ($label1).
+ baseFolder = create_folder("MailViewA");
+ [setUntagged, setTagged] = make_new_sets_in_folder(baseFolder,
+ [{}, {}]);
+ setTagged.addTag("$label1"); // Important, by default
+};
+
+function test_put_view_picker_on_toolbar() {
+ let toolbar = mc.e("mail-bar2");
+ toolbar.insertItem("mailviews-container", null);
+ mc.assertNode(mc.eid("mailviews-container"));
+}
+
+/**
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c97
+ */
+function test_save_view_as_folder() {
+ // - enter the folder
+ be_in_folder(baseFolder);
+
+ // - apply the mail view
+ // okay, mozmill is just not ready to click on the view picker...
+ // just call the ViewChange global. it's sad, but it has the same effects.
+ // at least, it does once we've caused the popups to get refreshed.
+ mc.window.RefreshAllViewPopups(mc.e("viewPickerPopup"));
+ mc.window.ViewChange(":$label1");
+ wait_for_all_messages_to_load();
+
+ // - save it
+ plan_for_modal_dialog("mailnews:virtualFolderProperties",
+ subtest_save_mail_view);
+ // we have to use value here because the option mechanism is not sophisticated
+ // enough.
+ mc.window.ViewChange(MailViewConstants.kViewItemVirtual);
+ wait_for_modal_dialog("mailnews:virtualFolderProperties");
+}
+
+function subtest_save_mail_view(savc) {
+ // - make sure the name is right
+ savc.assertValue(savc.eid("name"), baseFolder.prettyName + "-Important");
+
+ // - make sure the constraint is right
+ savc.assertValue(savc.aid("searchVal0", {crazyDeck: 0}), "$label1");
+
+ // - save it
+ savc.window.onOK();
+}
+
+function test_verify_saved_mail_view() {
+ // - make sure the folder got created
+ savedFolder = baseFolder.findSubFolder(baseFolder.prettyName + "-Important");
+ if (!savedFolder)
+ throw new Error("MailViewA-Important was not created!");
+
+ // - go in the folder and make sure the right messages are displayed
+ be_in_folder(savedFolder);
+ assert_messages_in_view(setTagged, mc);
+}
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/folder-display/test-message-window.js
@@ -0,0 +1,92 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test that we can open and close a standalone message display window from the
+ * folder pane.
+ */
+var MODULE_NAME = 'test-message-window';
+
+var RELATIVE_ROOT = '../shared-modules';
+var MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers'];
+
+var folder;
+var msgSet;
+
+function setupModule(module) {
+ let fdh = collector.getModule('folder-display-helpers');
+ fdh.installInto(module);
+ let wh = collector.getModule('window-helpers');
+ wh.installInto(module);
+
+ folder = create_folder("MessageWindowA");
+ // create three messages in the folder to display
+ [msgSet] = make_new_sets_in_folder(folder, [{count: 3}]);
+}
+
+/** The message window controller. */
+var msgc;
+
+function test_open_message_window() {
+ be_in_folder(folder);
+
+ // select the one and only message
+ let curMessage = select_click_row(0);
+
+ // display it
+ msgc = open_selected_message_in_new_window();
+ assert_selected_and_displayed(msgc, curMessage);
+}
+
+/**
+ * Use the "f" keyboard accelerator to navigate to the next message,
+ * and verify that it is indeed loaded.
+ */
+function test_navigate_to_next_message() {
+ msgc.keypress(null, "f", {});
+ wait_for_message_display_completion(msgc, true);
+ assert_selected_and_displayed(msgc, 1);
+}
+
+/**
+ * Close the window by hitting escape.
+ */
+function test_close_message_window() {
+ plan_for_window_close(msgc);
+ msgc.keypress(null, "VK_ESCAPE", {});
+ wait_for_window_close(msgc);
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/folder-display/test-right-click-middle-click.js
@@ -0,0 +1,313 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test the many horrors involving right-clicks, middle clicks, and selections.
+ */
+
+var MODULE_NAME = 'test-deletion-with-multiple-displays';
+
+var RELATIVE_ROOT = '../shared-modules';
+var MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers'];
+
+var folder;
+
+function setupModule(module) {
+ let fdh = collector.getModule('folder-display-helpers');
+ fdh.installInto(module);
+ let wh = collector.getModule('window-helpers');
+ wh.installInto(module);
+
+ folder = create_folder("RightClickMiddleClickA");
+ // we want exactly as many messages as we plan to delete, so that we can test
+ // that the message window and tabs close when they run out of things to
+ // to display.
+ make_new_sets_in_folder(folder, [{count: 20}]);
+}
+
+/**
+ * Make sure that a right-click when there is nothing currently selected does
+ * not cause us to display something, as well as correctly causing a transient
+ * selection to occur.
+ */
+function test_right_click_with_nothing_selected() {
+ be_in_folder(folder);
+
+ select_none();
+ assert_nothing_selected();
+
+ right_click_on_row(1);
+ assert_selected(1);
+ assert_displayed();
+
+ close_popup();
+ assert_nothing_selected();
+}
+
+/**
+ * One-thing selected, right-click on something else.
+ */
+function test_right_click_with_one_thing_selected() {
+ be_in_folder(folder);
+
+ select_click_row(0);
+ assert_selected_and_displayed(0);
+
+ right_click_on_row(1);
+ assert_selected(1);
+ assert_displayed(0);
+
+ close_popup();
+ assert_selected_and_displayed(0);
+}
+
+/**
+ * Many things selected, right-click on something that is not in that selection.
+ */
+function test_right_click_with_many_things_selected() {
+ be_in_folder(folder);
+
+ select_click_row(0);
+ select_shift_click_row(5);
+ assert_selected_and_displayed([0, 5]);
+
+ right_click_on_row(6);
+ assert_selected(6);
+ assert_displayed([0, 5]);
+
+ close_popup();
+ assert_selected_and_displayed([0, 5]);
+}
+
+/**
+ * One thing selected, right-click on that.
+ */
+function test_right_click_on_existing_single_selection() {
+ be_in_folder(folder);
+
+ select_click_row(3);
+ assert_selected_and_displayed(3);
+
+ right_click_on_row(3);
+ assert_selected_and_displayed(3);
+
+ close_popup();
+ assert_selected_and_displayed(3);
+}
+
+/**
+ * Many things selected, right-click somewhere in the selection.
+ */
+function test_right_click_on_existing_multi_selection() {
+ be_in_folder(folder);
+
+ select_click_row(3);
+ select_shift_click_row(6);
+ assert_selected_and_displayed([3, 6]);
+
+ right_click_on_row(5);
+ assert_selected_and_displayed([3, 6]);
+
+ close_popup();
+ assert_selected_and_displayed([3, 6]);
+}
+
+/**
+ * Middle clicking should open a message in a tab, but not affect our selection.
+ */
+function test_middle_click_with_nothing_selected() {
+ be_in_folder(folder);
+
+ select_none();
+ assert_nothing_selected();
+
+ let [tabMessage, curMessage] = middle_click_on_row(1);
+ // as of immediately right now, the tab opens in the foreground, but soon
+ // it will open in the background, so prepare the test for that...
+ switch_tab(tabMessage);
+ assert_selected_and_displayed(curMessage);
+ close_tab(tabMessage);
+
+ assert_nothing_selected();
+}
+
+/**
+ * One-thing selected, middle-click on something else.
+ */
+function test_middle_click_with_one_thing_selected() {
+ be_in_folder(folder);
+
+ select_click_row(0);
+ assert_selected_and_displayed(0);
+
+ let [tabMessage, curMessage] = middle_click_on_row(1);
+ switch_tab(tabMessage);
+ assert_selected_and_displayed(curMessage);
+ close_tab(tabMessage);
+
+ assert_selected_and_displayed(0);
+}
+
+/**
+ * Many things selected, middle-click on something that is not in that
+ * selection.
+ */
+function test_middle_click_with_many_things_selected() {
+ be_in_folder(folder);
+
+ select_click_row(0);
+ select_shift_click_row(5);
+ assert_selected_and_displayed([0, 5]);
+
+ let [tabMessage, curMessage] = middle_click_on_row(1);
+ switch_tab(tabMessage);
+ assert_selected_and_displayed(curMessage);
+ close_tab(tabMessage);
+
+ assert_selected_and_displayed([0, 5]);
+}
+
+/**
+ * One thing selected, middle-click on that.
+ */
+function test_middle_click_on_existing_single_selection() {
+ be_in_folder(folder);
+
+ select_click_row(3);
+ assert_selected_and_displayed(3);
+
+ let [tabMessage, curMessage] = middle_click_on_row(3);
+ switch_tab(tabMessage);
+ assert_selected_and_displayed(curMessage);
+ close_tab(tabMessage);
+
+ assert_selected_and_displayed(3);
+}
+
+/**
+ * Many things selected, right-click somewhere in the selection.
+ */
+function test_middle_click_on_existing_multi_selection() {
+ be_in_folder(folder);
+
+ select_click_row(3);
+ select_shift_click_row(6);
+ assert_selected_and_displayed([3, 6]);
+
+ let [tabMessage, curMessage] = middle_click_on_row(5);
+ switch_tab(tabMessage);
+ assert_selected_and_displayed(curMessage);
+ close_tab(tabMessage);
+
+ assert_selected_and_displayed([3, 6]);
+}
+
+/**
+ * Right-click on something and delete it, having no selection previously.
+ */
+function test_right_click_deletion_nothing_selected() {
+ be_in_folder(folder);
+
+ select_none();
+ assert_selected_and_displayed();
+
+ let delMessage = right_click_on_row(3);
+ delete_via_popup();
+ // eh, might as well make sure the deletion worked while we are here
+ assert_message_not_in_view(delMessage);
+
+ assert_selected_and_displayed();
+}
+
+/**
+ * We want to make sure that the selection post-delete still includes the same
+ * message (and that it is displayed). In order for this to be interesting,
+ * we want to make sure that we right-click delete a message above the selected
+ * message so there is a shift in row numbering.
+ */
+function test_right_click_deletion_one_other_thing_selected() {
+ be_in_folder(folder);
+
+ let curMessage = select_click_row(5);
+
+ let delMessage = right_click_on_row(3);
+ delete_via_popup();
+ assert_message_not_in_view(delMessage);
+
+ assert_selected_and_displayed(curMessage);
+}
+
+function test_right_click_deletion_many_other_things_selected() {
+ be_in_folder(folder);
+
+ select_click_row(4);
+ let messages = select_shift_click_row(6);
+
+ let delMessage = right_click_on_row(2);
+ delete_via_popup();
+ assert_message_not_in_view(delMessage);
+
+ assert_selected_and_displayed(messages);
+}
+
+function test_right_click_deletion_of_one_selected_thing() {
+ be_in_folder(folder);
+
+ let curMessage = select_click_row(2);
+
+ right_click_on_row(2);
+ delete_via_popup();
+ assert_message_not_in_view(curMessage);
+
+ if (!mc.folderDisplay.selectedCount)
+ throw new Error("We should have tried to select something!");
+}
+
+function test_right_click_deletion_of_many_selected_things() {
+ be_in_folder(folder);
+
+ select_click_row(2);
+ let messages = select_shift_click_row(4);
+
+ right_click_on_row(3);
+ delete_via_popup();
+ assert_messages_not_in_view(messages);
+
+ if (!mc.folderDisplay.selectedCount)
+ throw new Error("We should have tried to select something!");
+}
+
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/folder-display/test-selection.js
@@ -0,0 +1,157 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var MODULE_NAME = 'test-selection';
+
+var RELATIVE_ROOT = '../shared-modules';
+var MODULE_REQUIRES = ['folder-display-helpers'];
+
+// let us have 2 folders
+var folder = null, folder2 = null;
+
+var setupModule = function(module) {
+ let fdh = collector.getModule('folder-display-helpers');
+ fdh.installInto(module);
+
+ folder = create_folder("SelectionA");
+ folder2 = create_folder("SelectionB");
+ make_new_sets_in_folders([folder, folder2], [{count: 50}]);
+};
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c80
+function test_selection_on_entry() {
+ enter_folder(folder);
+ assert_nothing_selected();
+}
+
+function test_selection_extension() {
+ be_in_folder(folder);
+
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c79 (was good)
+ select_click_row(1);
+ select_control_click_row(2);
+ press_delete();
+ assert_selected_and_displayed(1);
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c79 (was bad)
+ select_click_row(2);
+ select_control_click_row(1);
+ press_delete();
+ assert_selected_and_displayed(1);
+
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c87 first bit
+ press_delete();
+ assert_selected_and_displayed(1);
+}
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c87 last bit
+function test_selection_last_message_deleted() {
+ be_in_folder(folder);
+ select_click_row(-1);
+ press_delete();
+ assert_selected_and_displayed(-1);
+}
+
+
+function test_selection_persists_through_threading_changes() {
+ be_in_folder(folder);
+
+ make_display_unthreaded();
+ let message = select_click_row(3);
+ make_display_threaded();
+ assert_selected_and_displayed(message);
+ make_display_grouped();
+ assert_selected_and_displayed(message);
+}
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c82 2nd half
+function test_no_selection_persists_through_threading_changes() {
+ be_in_folder(folder);
+
+ make_display_unthreaded();
+ select_none();
+ make_display_threaded();
+ assert_nothing_selected();
+ make_display_grouped();
+ assert_nothing_selected();
+ make_display_unthreaded();
+}
+
+function test_selection_persists_through_folder_tab_changes() {
+ let tab1 = be_in_folder(folder);
+
+ select_click_row(2);
+
+ let tab2 = open_folder_in_new_tab(folder2);
+ assert_nothing_selected();
+
+ switch_tab(tab1);
+ assert_selected_and_displayed(2);
+
+ switch_tab(tab2);
+ assert_nothing_selected();
+ select_click_row(3);
+
+ switch_tab(tab1);
+ assert_selected_and_displayed(2);
+ select_shift_click_row(4); // 2-4 selected
+ assert_selected_and_displayed([2,4]); // ensures multi-message summary
+
+ switch_tab(tab2);
+ assert_selected_and_displayed(3);
+
+ close_tab(tab2);
+ assert_selected_and_displayed([2,4]);
+}
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c87
+/**
+ * Verify that we scroll to new messages when we enter a folder.
+ */
+function test_enter_scroll_to_new() {
+ // be in the folder
+ be_in_folder(folder);
+ // make sure the sort is ascending...
+ mc.folderDisplay.view.sortAscending();
+ // leave the folder so that the messages get marked as read
+ enter_folder(folder.rootFolder);
+ // add a new message, and make sure it is new
+ let newSet = make_new_sets_in_folder(folder, [{count: 1}]);
+ // enter the folder
+ enter_folder(folder);
+ // make sure it (which must be the last row) is visible
+ assert_visible(-1);
+}
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/folder-display/test-summarization.js
@@ -0,0 +1,221 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * Test that summarization happens at the right time, that it clears itself at
+ * the right time, that it waits for selection stability when recently
+ * summarized, and that summarization does not break under tabbing.
+ *
+ * Because most of the legwork is done automatically by
+ * test-folder-display-helpers, the more basic tests may look like general
+ * selection / tabbing tests, but are intended to specifically exercise the
+ * summarization logic and edge cases. (Although general selection tests and
+ * tab tests may do the same thing too...)
+ *
+ * Things we don't test but should:
+ * - The difference between thread summary and multi-message summary.
+ */
+
+var MODULE_NAME = 'test-summarization';
+
+var RELATIVE_ROOT = '../shared-modules';
+var MODULE_REQUIRES = ['folder-display-helpers'];
+
+var folder;
+var thread1, thread2, msg1, msg2;
+
+var setupModule = function(module) {
+ let fdh = collector.getModule('folder-display-helpers');
+ fdh.installInto(module);
+
+ folder = create_folder("SummarizationA");
+ thread1 = create_thread(10);
+ msg1 = create_thread(1);
+ thread2 = create_thread(10);
+ msg2 = create_thread(1);
+ add_sets_to_folders([folder], [thread1, msg1, thread2, msg2]);
+};
+
+function test_basic_summarization() {
+ be_in_folder(folder);
+
+ // - make sure we get a summary
+ select_click_row(0);
+ select_shift_click_row(5);
+ // this will verify a multi-message display is happening
+ assert_selected_and_displayed([0, 5]);
+}
+
+function test_summarization_goes_away() {
+ select_none();
+ assert_nothing_selected();
+}
+
+/**
+ * Verify that we update summarization when switching amongst tabs.
+ */
+function test_folder_tabs_update_correctly() {
+ // tab with summary
+ let tabA = be_in_folder(folder);
+ select_click_row(0);
+ select_control_click_row(2);
+ assert_selected_and_displayed(0, 2);
+
+ // tab with nothing
+ let tabB = open_folder_in_new_tab(folder);
+ assert_nothing_selected();
+
+ // correct changes, none <=> summary
+ switch_tab(tabA);
+ assert_selected_and_displayed(0, 2);
+ switch_tab(tabB);
+ assert_nothing_selected();
+
+ // correct changes, one <=> summary
+ select_click_row(0);
+ assert_selected_and_displayed(0);
+ switch_tab(tabA);
+ assert_selected_and_displayed(0, 2);
+ switch_tab(tabB);
+ assert_selected_and_displayed(0);
+
+ // correct changes, summary <=> summary
+ select_shift_click_row(3);
+ assert_selected_and_displayed([0, 3]);
+ switch_tab(tabA);
+ assert_selected_and_displayed(0, 2);
+ switch_tab(tabB);
+ assert_selected_and_displayed([0, 3]);
+
+ // closing tab returns state correctly...
+ close_tab(tabB);
+ assert_selected_and_displayed(0, 2);
+}
+
+function test_message_tabs_update_correctly() {
+ let tabFolder = be_in_folder(folder);
+ let message = select_click_row(0);
+ assert_selected_and_displayed(0);
+
+ let tabMessage = open_selected_message_in_new_tab();
+ assert_selected_and_displayed(message);
+
+ switch_tab(tabFolder);
+ select_shift_click_row(2);
+ assert_selected_and_displayed([0, 2]);
+
+ switch_tab(tabMessage);
+ assert_selected_and_displayed(message);
+
+ switch_tab(tabFolder);
+ assert_selected_and_displayed([0, 2]);
+
+ close_tab(tabMessage);
+}
+
+/**
+ * Test the stabilization logic by making the stabilization interval absurd and
+ * then manually clearing things up.
+ */
+function test_selection_stabilization_logic() {
+ // make sure all summarization has run to completion.
+ mc.sleep(0);
+ // make it inconceivable that the timeout happens.
+ mc.window.MessageDisplayWidget.prototype
+ .SUMMARIZATION_SELECTION_STABILITY_INTERVAL_MS = 10000;
+ // does not summarize anything, does not affect timer
+ select_click_row(0);
+ // does summarize things. timer will be tick tick ticking!
+ select_shift_click_row(1);
+ // verify that things were summarized...
+ assert_selected_and_displayed([0, 1]);
+ // save the set of messages so we can verify the summary sticks to this.
+ let messages = mc.folderDisplay.selectedMessages;
+
+ // make sure the
+
+ // this will not summarize!
+ select_shift_click_row(2);
+ // verify that our summary is still just 0 and 1.
+ assert_selection_summarized(mc, messages);
+
+ // - put it back, the way it was
+ // oh put it back the way it was
+ // ...
+ // That's right folks, a 'Lil Abner reference.
+ // ...
+ // Culture!
+ // ...
+ // I'm already embarassed I wrote that.
+ mc.window.MessageDisplayWidget.prototype
+ .SUMMARIZATION_SELECTION_STABILITY_INTERVAL_MS = 0;
+ // (we did that because the stability logic is going to schedule another guard
+ // timer when we manually trigger it, and we want that to clear immediately.)
+
+ // - pretend the timer fired.
+ // we need to de-schedule the timer, but do not need to clear the variable
+ // because it will just get overwritten anyways
+ mc.window.clearTimeout(mc.messageDisplay._summaryStabilityTimeout);
+ mc.messageDisplay._showSummary(true);
+
+ // - the summary should now be up-to-date
+ assert_selected_and_displayed([0, 2]);
+}
+
+
+function test_summarization_thread_detection() {
+ select_none();
+ assert_nothing_selected();
+ make_display_threaded();
+ select_click_row(0);
+ select_shift_click_row(9);
+ let messages = mc.folderDisplay.selectedMessages;
+ toggle_thread_row(0);
+ assert_selection_summarized(mc, messages);
+ // count the number of messages represented
+ assert_summary_contains_N_divs('wrappedsender', 10);
+ select_shift_click_row(1);
+ // this should have shifted to the multi-message view
+ assert_summary_contains_N_divs('wrappedsender', 0);
+ assert_summary_contains_N_divs('wrappedsubject', 2);
+ select_none();
+ assert_nothing_selected();
+ select_click_row(1); // select a single message
+ select_shift_click_row(2); // add a thread
+ assert_summary_contains_N_divs('wrappedsender', 0);
+ assert_summary_contains_N_divs('wrappedsubject', 2);
+}
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/folder-display/test-tabs-simple.js
@@ -0,0 +1,178 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test that opening new folder and message tabs has the expected result and
+ * that closing them doesn't break anything.
+ */
+var MODULE_NAME = 'test-tabs-simple';
+
+var RELATIVE_ROOT = '../shared-modules';
+var MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers'];
+
+var folderA, folderB, setA, setB;
+
+function setupModule(module) {
+ let fdh = collector.getModule('folder-display-helpers');
+ fdh.installInto(module);
+ let wh = collector.getModule('window-helpers');
+ wh.installInto(module);
+
+ folderA = create_folder("TabsSimpleA");
+ folderB = create_folder("TabsSimpleB");
+
+ // We will verify we are seeing the right folder by checking that it has the
+ // right messages in it.
+ [setA] = make_new_sets_in_folder(folderA, [{}]);
+ [setB] = make_new_sets_in_folder(folderB, [{}]);
+}
+
+/** The tabs in our test. */
+var tabFolderA, tabFolderB, tabMessageA, tabMessageB;
+/** The message that we selected for tab display, to check it worked right. */
+var messageA, messageB;
+
+/**
+ * Make sure the default tab works right.
+ */
+function test_open_folder_a() {
+ tabFolderA = be_in_folder(folderA);
+ assert_messages_in_view(setA);
+ assert_nothing_selected();
+}
+
+/**
+ * Open tab b, make sure it works right.
+ */
+function test_open_folder_b_in_tab() {
+ tabFolderB = open_folder_in_new_tab(folderB);
+ assert_messages_in_view(setB);
+ assert_nothing_selected();
+}
+
+/**
+ * Go back to tab/folder A and make sure we change correctly.
+ */
+function test_switch_to_tab_folder_a() {
+ switch_tab(tabFolderA);
+ assert_messages_in_view(setA);
+ assert_nothing_selected();
+}
+
+/**
+ * Select a message in folder A and open it in a new window, making sure that
+ * the displayed message is the right one.
+ */
+function test_open_message_a_in_tab() {
+ messageA = select_click_row(0);
+ tabMessageA = open_selected_message_in_new_tab();
+ assert_selected_and_displayed(messageA);
+}
+
+/**
+ * Go back to tab/folder B and make sure we change correctly.
+ */
+function test_switch_to_tab_folder_b() {
+ switch_tab(tabFolderB);
+ assert_messages_in_view(setB);
+ assert_nothing_selected();
+}
+
+/**
+ * Select a message in folder B and open it in a new window, making sure that
+ * the displayed message is the right one.
+ */
+function test_open_message_b_in_tab() {
+ messageB = select_click_row(0);
+ tabMessageB = open_selected_message_in_new_tab();
+ assert_selected_and_displayed(messageB);
+}
+
+/**
+ * Switch to message tab A.
+ */
+function test_switch_to_message_a() {
+ switch_tab(tabMessageA);
+ assert_selected_and_displayed(messageA);
+}
+
+/**
+ * Close message tab A (when it's in the foreground).
+ */
+function test_close_message_a() {
+ close_tab();
+ // our current tab is now undefined for the purposes of this test.
+}
+
+/**
+ * Make sure all the other tabs are still happy.
+ */
+function test_tabs_are_still_happy() {
+ switch_tab(tabFolderB);
+ assert_messages_in_view(setB);
+ assert_selected_and_displayed(messageB);
+
+ switch_tab(tabMessageB);
+ assert_selected_and_displayed(messageB);
+
+ switch_tab(tabFolderA);
+ assert_messages_in_view(setA);
+ assert_selected_and_displayed(messageA);
+}
+
+/**
+ * Close message tab B (when it's in the background).
+ */
+function test_close_message_b() {
+ close_tab(tabMessageB);
+ // we should still be on folder A
+ assert_messages_in_view(setA);
+ assert_selected_and_displayed(messageA);
+}
+
+/**
+ * Switch to tab B, close it, make sure we end up on tab A.
+ */
+function test_close_folder_b() {
+ switch_tab(tabFolderB);
+ assert_messages_in_view(setB);
+ assert_selected_and_displayed(messageB);
+
+ close_tab();
+ assert_messages_in_view(setA);
+ assert_selected_and_displayed(messageA);
+}
--- a/mail/test/mozmill/runtest.py
+++ b/mail/test/mozmill/runtest.py
@@ -66,49 +66,48 @@ class ThunderTestProfile(mozrunner.Thund
'mail.winsearch.enable': False,
'mail.winsearch.firstRunDone': True,
'mail.spotlight.enable': False,
'mail.spotlight.firstRunDone': True,
# disable address books for undisclosed reasons
'ldap_2.servers.osx.position': 0,
'ldap_2.servers.oe.position': 0,
# other unknown voodoo
- 'dom.max_chrome_script_run_time' : 200,
- 'dom.max_script_run_time' : 0,
+ # -- dummied up local accounts to stop the account wizard
'mail.account.account1.server' : "server1",
'mail.account.account2.identities' : "id1",
'mail.account.account2.server' : "server2",
'mail.accountmanager.accounts' : "account1,account2",
'mail.accountmanager.defaultaccount' : "account2",
'mail.accountmanager.localfoldersserver' : "server1",
- 'mail.identity.id1.fullName' : "Tinderbox",
+ 'mail.identity.id1.fullName' : "Tinderbox",
'mail.identity.id1.smtpServer' : "smtp1",
- 'mail.identity.id1.useremail' : "tinderbox@invalid.com",
+ 'mail.identity.id1.useremail' : "tinderbox@invalid.com",
'mail.identity.id1.valid' : True,
- 'mail.root.none-rel' : "[ProfD]Mail",
+ 'mail.root.none-rel' : "[ProfD]Mail",
'mail.root.pop3-rel' : "[ProfD]Mail",
'mail.server.server1.directory-rel' : "[ProfD]Mail/Local Folders",
'mail.server.server1.hostname' : "Local Folders",
- 'mail.server.server1.name' : "Local Folders",
+ 'mail.server.server1.name' : "Local Folders",
'mail.server.server1.type' : "none",
'mail.server.server1.userName' : "nobody",
'mail.server.server2.check_new_mail' : False,
'mail.server.server2.directory-rel' : "[ProfD]Mail/tinderbox",
'mail.server.server2.download_on_biff' : True,
- 'mail.server.server2.hostname' : "tinderbox",
- 'mail.server.server2.login_at_startup' : False,
- 'mail.server.server2.name' : "tinderbox@invalid.com",
- 'mail.server.server2.type' : "pop3",
- 'mail.server.server2.userName' : "tinderbox",
- 'mail.smtp.defaultserver' : "smtp1",
- 'mail.smtpserver.smtp1.hostname' : "tinderbox",
- 'mail.smtpserver.smtp1.username' : "tinderbox",
- 'mail.smtpservers' : "smtp1",
- 'mail.startup.enabledMailCheckOnce' : True,
- 'mailnews.start_page_override.mstone' : "ignore",
+ 'mail.server.server2.hostname' : "tinderbox",
+ 'mail.server.server2.login_at_startup' : False,
+ 'mail.server.server2.name' : "tinderbox@invalid.com",
+ 'mail.server.server2.type' : "pop3",
+ 'mail.server.server2.userName' : "tinderbox",
+ 'mail.smtp.defaultserver' : "smtp1",
+ 'mail.smtpserver.smtp1.hostname' : "tinderbox",
+ 'mail.smtpserver.smtp1.username' : "tinderbox",
+ 'mail.smtpservers' : "smtp1",
+ 'mail.startup.enabledMailCheckOnce' : True,
+ 'mailnews.start_page_override.mstone' : "ignore",
}
def __init__(self, default_profile=None, profile=None, create_new=True,
plugins=[], preferences={}):
self.init_env()
self.init_paths()
mozrunner.Profile.__init__(self, None, None, True, plugins, preferences)
@@ -120,17 +119,21 @@ class ThunderTestProfile(mozrunner.Thund
self.automation_dir = os.path.join(self.obj_dir, 'mozilla', 'build')
sys.path.append(self.automation_dir)
import automation
self.profile_dir = os.path.join(self.obj_dir, 'mozilla',
'_tests', 'leakprofile')
# XXX tidy up
if automation.IS_MAC:
- self.bin_dir = os.path.join(self.obj_dir, 'mozilla', 'dist', 'ShredderDebug.app', 'Contents', 'MacOS')
+ if automation.IS_DEBUG_BUILD:
+ appName = 'ShredderDebug.app'
+ else:
+ appName = 'Shredder.app'
+ self.bin_dir = os.path.join(self.obj_dir, 'mozilla', 'dist', appName, 'Contents', 'MacOS')
appname = 'thunderbird-bin'
else:
self.bin_dir = os.path.join(self.obj_dir, 'mozilla', 'dist', 'bin')
appname = 'thunderbird'
if automation.IS_WIN32:
appname += '.exe'
self.app_path = os.path.join(self.bin_dir, appname)
@@ -139,17 +142,17 @@ class ThunderTestProfile(mozrunner.Thund
def find_src_dir(self):
curdir = os.getcwd()
while not os.path.isdir(os.path.join(curdir, '.hg')):
curdir, olddir = os.path.split(curdir)
if curdir == '':
raise Exception("unable to figure out src_dir")
- return curdir
+ return os.path.expanduser(os.path.expandvars(curdir))
def find_obj_dir(self):
if 'MOZCONFIG' in os.environ:
mozconfig_path = os.environ['MOZCONFIG']
else:
mozconfig_path = os.path.join(self.src_dir, '.mozconfig.mk')
guess_path = os.path.join(self.src_dir, 'mozilla/build/autoconf/config.guess')
@@ -157,26 +160,26 @@ class ThunderTestProfile(mozrunner.Thund
config_guess = config_guess.strip()
f = open(mozconfig_path, 'rt')
for line in f:
if 'MOZ_OBJDIR' in line:
varpath = line.split('=')[1].strip()
varpath = varpath.replace('@TOPSRCDIR@', self.src_dir)
varpath = varpath.replace('$(TOPSRCDIR)', self.src_dir)
varpath = varpath.replace('@CONFIG_GUESS@',config_guess)
- return varpath
+ return os.path.expanduser(os.path.expandvars(varpath))
f.close()
raise Exception("unable to figure out obj_dir")
def init_env(self):
self.base_env = dict(os.environ)
# note, we do NOT want to set NO_EM_RESTART or jsbridge wouldn't work
# avoid dialogs on windows
- self.base_env['XPCOM_DEBUG_BREAK'] = 'warn'
+ self.base_env['XPCOM_DEBUG_BREAK'] = 'stack'
# do not reuse an existing instance
self.base_env['MOZ_NO_REMOTE'] = '1'
def _run(self, *args, **extraenv):
env = self.base_env.copy()
env.update(extraenv)
allArgs = [self.app_path]
allArgs.extend(args)
@@ -213,10 +216,66 @@ class ThunderTestRunner(mozrunner.Thunde
def find_binary(self):
return self.profile.app_path
class ThunderTestCLI(mozmill.CLI):
profile_class = ThunderTestProfile
runner_class = ThunderTestRunner
+TEST_RESULTS = []
+# override mozmill's default logging case, which I hate.
+def logFailure(obj):
+ FAILURE_LIST.append(obj)
+def logEndTest(obj):
+ TEST_RESULTS.append(obj)
+#mozmill.LoggerListener.cases['mozmill.fail'] = logFailure
+mozmill.LoggerListener.cases['mozmill.endTest'] = logEndTest
+
+def prettifyFilename(path):
+ lslash = path.rfind('/')
+ if lslash != -1:
+ return path[lslash+1:]
+ else:
+ return path
+
+def prettyPrintException(e):
+ print ' EXCEPTION:', e.get('message', 'no message!')
+ print ' at:', prettifyFilename(e.get('fileName', 'nonesuch')), 'line', e.get('lineNumber', 0)
+ if 'stack' in e:
+ for line in e['stack'].splitlines():
+ if not line:
+ continue
+ if line[0] == "(":
+ funcname = None
+ elif line[0] == "@":
+ # this is probably the root, don't care
+ continue
+ else:
+ funcname = line[:line.find('@')]
+ pathAndLine = line[line.rfind('@')+1:]
+ rcolon = pathAndLine.rfind(':')
+ if rcolon != -1:
+ path = pathAndLine[:rcolon]
+ line = pathAndLine[rcolon+1:]
+ else:
+ path = pathAndLine
+ line = 0
+ if funcname:
+ print ' ', funcname, prettifyFilename(path), line
+ else:
+ print ' ', prettifyFilename(path), line
+
+
+import pprint
+def prettyPrintResults():
+ for result in TEST_RESULTS:
+ #pprint.pprint(result)
+ print 'TEST', result['name'], len(result['fails']) and "FAILED" or "PASSED"
+ for failure in result['fails']:
+ if 'exception' in failure:
+ prettyPrintException(failure['exception'])
+
+import atexit
+atexit.register(prettyPrintResults)
+
if __name__ == '__main__':
ThunderTestCLI().run()
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/search-window/test-search-window.js
@@ -0,0 +1,178 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Tests:
+ * - https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c96 first para
+ */
+var MODULE_NAME = 'test-search-window';
+
+var RELATIVE_ROOT = '../shared-modules';
+var MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers'];
+
+function setupModule(module) {
+ let fdh = collector.getModule('folder-display-helpers');
+ fdh.installInto(module);
+ let wh = collector.getModule('window-helpers');
+ wh.installInto(module);
+}
+
+var folder, setFoo, setBar, setFooBar;
+
+/**
+ * Create some messages that our constraint below will satisfy
+ */
+function test_create_messages() {
+ folder = create_folder("SearchWindowA");
+ [setFoo, setBar, setFooBar] =
+ make_new_sets_in_folder(folder, [{subject: "foo"}, {subject: "bar"},
+ {subject: "foo bar"}]);
+}
+
+/**
+ * The search window controller.
+ */
+var swc = null;
+
+/**
+ * Bring up the search window.
+ */
+function test_show_search_window() {
+ // put us in the folder we care about so it defaults to that
+ be_in_folder(folder);
+
+ // push control-shift-F, wait for it to show
+ plan_for_new_window("mailnews:search");
+ mc.keypress(null, "f", {shiftKey: true, ctrlKey: true});
+ swc = wait_for_new_window("mailnews:search");
+}
+
+/**
+ * Set up the search.
+ */
+function test_enter_some_stuff() {
+ // - turn off search subfolders
+ // (we're not testing the UI, direct access is fine)
+ swc.e("checkSearchSubFolders").removeAttribute("checked");
+
+ // - put "foo" in the subject contains box
+ // Each filter criterion is a listitem in the listbox with id=searchTermList.
+ // Each filter criterion has id "searchRowN", and the textbox has id
+ // "searchValN" exposing the value on attribute "value".
+ // XXX I am having real difficulty getting the click/type pair to actually
+ // get the text in there reliably. I am just going to poke things directly
+ // into the text widget. (We used to use .aid instead of .a with swc.click
+ // and swc.type.)
+ let searchVal0 = swc.a("searchVal0", {crazyDeck: 0});
+ searchVal0.value = "foo";
+
+ // - add another subject box
+ let plusButton = swc.eid("searchRow0", {tagName: "button", label: "+"});
+ swc.click(plusButton);
+
+ // - put "bar" in it
+ let searchVal1 = swc.a("searchVal1", {crazyDeck: 0});
+ searchVal1.value = "bar";
+}
+
+/**
+ * Trigger the search, make sure the right results show up.
+ */
+function test_go_search() {
+ // - Trigger the search
+ // The "Search" button has id "search-button"
+ swc.click(swc.eid("search-button"));
+ wait_for_all_messages_to_load(swc);
+
+ // - Verify we got the right messages
+ assert_messages_in_view(setFooBar, swc);
+
+ // - Click the "Save as Search Folder" button, id "saveAsVFButton"
+ // This will create a virtual folder properties dialog...
+ // (label: "New Saved Search Folder", source: virtualFolderProperties.xul
+ // no windowtype, id: "virtualFolderPropertiesDialog")
+ plan_for_modal_dialog("mailnews:virtualFolderProperties",
+ subtest_save_search);
+ swc.click(swc.eid("saveAsVFButton"));
+ wait_for_modal_dialog("mailnews:virtualFolderProperties");
+}
+
+/**
+ * Save the search, making sure the constraints propagated.
+ */
+function subtest_save_search(savc) {
+ // - make sure our constraint propagated
+ // The query constraints are displayed using the same widgets (and code) that
+ // we used to enter them, so it's very similar to check.
+ let searchVal0 = savc.aid("searchVal0", {crazyDeck: 0});
+ savc.assertNode(searchVal0);
+ savc.assertValue(searchVal0, "foo");
+ let searchVal1 = savc.aid("searchVal1", {crazyDeck: 0});
+ savc.assertNode(searchVal1);
+ savc.assertValue(searchVal1, "bar");
+
+ // - name the search
+ // I am having no luck with click/type on XUL things. workaround it.
+ savc.e("name").value = "SearchSaved";
+ savc.window.doEnabling();
+
+ // - save it!
+ // this will close the dialog, which wait_for_modal_dialog is making sure
+ // happens.
+ savc.window.onOK();
+}
+
+function test_close_search_window() {
+ // now close the search window
+ plan_for_window_close(swc);
+ swc.keypress(null, "VK_ESCAPE", {});
+ wait_for_window_close(swc);
+ swc = null;
+}
+
+/**
+ * Make sure the folder showed up with the right name, and that displaying it
+ * has the right contents.
+ */
+function test_verify_saved_search() {
+ let savedFolder = folder.findSubFolder("SearchSaved");
+ if (savedFolder == null)
+ throw new Error("Saved folder did not show up.");
+
+ be_in_folder(savedFolder);
+ assert_messages_in_view(setFooBar);
+}
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/shared-modules/test-folder-display-helpers.js
@@ -0,0 +1,1078 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cu = Components.utils;
+
+var elib = {};
+Cu.import('resource://mozmill/modules/elementslib.js', elib);
+var EventUtils = {};
+Cu.import('resource://mozmill/modules/EventUtils.js', EventUtils);
+var mozmill = {};
+Cu.import('resource://mozmill/modules/mozmill.js', mozmill);
+var controller = {};
+Cu.import('resource://mozmill/modules/controller.js', controller);
+var frame = {};
+Cu.import('resource://mozmill/modules/frame.js', frame);
+var os = {};
+Cu.import('resource://mozmill/stdlib/os.js', os);
+
+const MODULE_NAME = 'folder-display-helpers';
+
+const RELATIVE_ROOT = '../shared-modules';
+// we need window-helpers for augment_controller
+const MODULES_REQUIRES = ['window-helpers'];
+
+const nsMsgViewIndex_None = 0xffffffff;
+
+
+const DO_NOT_EXPORT = {
+ // magic globals
+ MODULE_NAME: true, DO_NOT_EXPORT: true,
+ // imported modules
+ elib: true, mozmill: true, controller: true, frame: true, os: true,
+ // convenience constants
+ Ci: true, Cc: true, Cu: true, Cr: true,
+ // useful constants
+ nsMsgViewIndex_None: true,
+ // internal setup functions
+ setupModule: true, setupAccountStuff: true,
+ // internal setup flags
+ initialized: false,
+ // other libraries we use
+ windowHelper: true
+};
+
+var mainController = null;
+/** convenience shorthand for the mainController. */
+var mc;
+/** the index of the current 'other' tab */
+var otherTab;
+
+// These are pseudo-modules setup by setupModule:
+var messageGenerator;
+var messageModifier;
+var viewWrapperTestUtils;
+// (end pseudo-modules)
+
+var msgGen;
+
+var gLocalIncomingServer = null;
+var gLocalInboxFolder = null;
+
+var rootFolder = null;
+
+// the windowHelper module
+var windowHelper;
+
+var initialized = false;
+function setupModule() {
+ if (initialized)
+ return;
+ initialized = true;
+
+ // The xpcshell test resources assume they are loaded into a single global
+ // namespace, so we need to help them out to maintain their delusion.
+ messageGenerator = load_via_src_path(
+ 'mailnews/test/resources/messageGenerator.js');
+ messageModifier = load_via_src_path(
+ 'mailnews/test/resources/messageModifier.js');
+ viewWrapperTestUtils = load_via_src_path(
+ 'mailnews/test/resources/viewWrapperTestUtils.js');
+ // desired global types...
+ viewWrapperTestUtils.SyntheticMessageSet =
+ messageModifier.SyntheticMessageSet;
+ viewWrapperTestUtils.do_throw = function(aMsg) {
+ throw new Error(aMsg);
+ };
+ // viewWrapperTestUtils wants a gMessageGenerator (and so do we)
+ msgGen = new messageGenerator.MessageGenerator();
+ viewWrapperTestUtils.gMessageGenerator = msgGen;
+ viewWrapperTestUtils.gMessageScenarioFactory =
+ new messageGenerator.MessageScenarioFactory(msgGen);
+
+ make_new_sets_in_folders = make_new_sets_in_folder =
+ viewWrapperTestUtils.make_new_sets_in_folders;
+ add_sets_to_folders = viewWrapperTestUtils.add_sets_to_folders;
+ viewWrapperTestUtils.Ci = Ci;
+ viewWrapperTestUtils.Cu = Cu;
+ viewWrapperTestUtils.Cc = Cc;
+
+ // use window-helper's augment_controller method to get our extra good stuff
+ // we need.
+ windowHelper = collector.getModule('window-helpers');
+ mc = mainController = windowHelper.wait_for_existing_window("mail:3pane");
+ windowHelper.augment_controller(mc);
+
+ setupAccountStuff();
+}
+
+/**
+ * Install this module into the provided module.
+ */
+function installInto(module) {
+ setupModule();
+
+ // now copy everything into the module they provided to us...
+ let us = collector.getModule('folder-display-helpers');
+ for each (let [key, value] in Iterator(us)) {
+ if (!(key in DO_NOT_EXPORT) &&
+ key[0] != "_")
+ module[key] = value;
+ }
+}
+
+function setupAccountStuff() {
+ // Create a local account to work with folders.
+ // (Note this gives you an Outbox and Trash folder by default).
+ let acctMgr = Components.classes["@mozilla.org/messenger/account-manager;1"]
+ .getService(Components.interfaces.nsIMsgAccountManager);
+ //acctMgr.createLocalMailAccount();
+
+ gLocalIncomingServer = acctMgr.localFoldersServer;
+
+ rootFolder = gLocalIncomingServer.rootMsgFolder;
+ // Note: Inbox is not created automatically when there is no deferred server,
+ // so we need to create it.
+ gLocalInboxFolder = rootFolder.addSubfolder("Inbox");
+ // a local inbox should have a Mail flag!
+ gLocalInboxFolder.setFlag(Components.interfaces.nsMsgFolderFlags.Mail);
+
+ // Force an initialization of the Inbox folder database.
+ var folderName = gLocalInboxFolder.prettiestName;
+
+ gLocalInboxFolder = rootFolder.getChildNamed("Inbox");
+}
+
+/*
+ * Although we all agree that the use of generators when dealing with async
+ * operations is awesome, the mozmill idiom is for calls to be synchronous and
+ * just spin event loops when they need to wait for things to happen. This
+ * does make the test code significantly less confusing, so we do it too.
+ * All of our operations are synchronous and just spin until they are happy.
+ */
+
+const NORMAL_TIMEOUT = 6000;
+const FAST_INTERVAL = 100;
+
+/**
+ * Create a folder and rebuild the folder tree view.
+ */
+function create_folder(aFolderName) {
+ let folder = rootFolder.addSubfolder(aFolderName);
+ mc.folderTreeView.mode = "all";
+ return folder;
+}
+
+/**
+ * Create a thread with the specified number of messages in it.
+ */
+function create_thread(aCount) {
+ return new viewWrapperTestUtils.SyntheticMessageSet(viewWrapperTestUtils.gMessageScenarioFactory.directReply(aCount));
+}
+
+/**
+ * Make sure we are entering the folder from not having been in the folder. We
+ * will leave the folder and come back if we have to.
+ */
+function enter_folder(aFolder) {
+ // if we're already selected, go back to the root...
+ if (mc.folderDisplay.displayedFolder == aFolder)
+ enter_folder(aFolder.rootFolder);
+
+ mc.folderTreeView.selectFolder(aFolder);
+ wait_for_all_messages_to_load();
+ // and drain the event queue
+ controller.sleep(0);
+}
+
+/**
+ * Make sure we are in the given folder, entering it if we were not.
+ *
+ * @return The tab info of the current tab (a more persistent identifier for
+ * tabs than the index, which will change as tabs open/close).
+ */
+function be_in_folder(aFolder) {
+ if (mc.folderDisplay.displayedFolder != aFolder)
+ enter_folder(aFolder);
+ return mc.tabmail.currentTabInfo;
+}
+
+/**
+ * Create a new tab displaying a folder, making that tab the current tab.
+ *
+ * @return The tab info of the current tab (a more persistent identifier for
+ * tabs than the index, which will change as tabs open/close).
+ */
+function open_folder_in_new_tab(aFolder) {
+ // save the current tab as the 'other' tab
+ otherTab = mc.tabmail.currentTabInfo;
+ mc.tabmail.openTab("folder", aFolder);
+ wait_for_all_messages_to_load();
+ return mc.tabmail.currentTabInfo;
+}
+
+/**
+ * Create a new tab displaying the currently selected message, making that tab
+ * the current tab. We block until the message finishes loading.
+ *
+ * @return The tab info of the current tab (a more persistent identifier for
+ * tabs than the index, which will change as tabs open/close).
+ */
+function open_selected_message_in_new_tab() {
+ // get the current tab count so we can make sure the tab actually opened.
+ let preCount = mc.tabmail.tabContainer.childNodes.length;
+
+ // save the current tab as the 'other' tab
+ otherTab = mc.tabmail.currentTabInfo;
+
+ mc.window.MsgOpenNewTabForMessage();
+ wait_for_message_display_completion(mc, true);
+
+ // check that the tab count increased
+ if (mc.tabmail.tabContainer.childNodes.length != preCount + 1)
+ throw new Error("The tab never actually got opened!");
+
+ return mc.tabmail.currentTabInfo;
+}
+
+/**
+ * Create a new window displaying the currently selected message. We do not
+ * return until the message has finished loading.
+ *
+ * @return The MozmillController-wrapped new window.
+ */
+function open_selected_message_in_new_window() {
+ windowHelper.plan_for_new_window("mail:messageWindow");
+ mc.window.MsgOpenNewWindowForMessage();
+ let msgc = windowHelper.wait_for_new_window("mail:messageWindow");
+ wait_for_message_display_completion(msgc, true);
+ return msgc;
+}
+
+/**
+ * Switch to another tab. If no tab is specified, we switch to the 'other' tab.
+ * That is the last tab we used, most likely the tab that was current when we
+ * created this tab.
+ *
+ * @param aNewTab Optional, index of the other tab to switch to.
+ */
+function switch_tab(aNewTab) {
+ let targetTab = (aNewTab != null) ? aNewTab : otherTab;
+ // now the current tab will be the 'other' tab after we switch
+ otherTab = mc.tabmail.currentTabInfo;
+ mc.tabmail.switchToTab(targetTab);
+ wait_for_message_display_completion();
+}
+
+/**
+ * Assert that the given tab's title is based on the provided folder or
+ * message.
+ *
+ * @param aTab A Tab.
+ * @param aWhat Either an nsIMsgFolder or an nsIMsgDBHdr
+ */
+function assert_tab_titled_from(aTab, aWhat) {
+ let text;
+ if (aWhat instanceof Ci.nsIMsgFolder)
+ text = aWhat.prettiestName;
+ else if (aWhat instanceof Ci.nsIMsgDBHdr)
+ text = aWhat.mime2DecodedSubject;
+
+ if (aTab.title.indexOf(text) == -1)
+ throw new Error("Tab title should include '" + text + "' but does not." +
+ " (Current title: '" + aTab.title + "'");
+}
+
+/**
+ * Close a tab. If no tab is specified, it is assumed you want to close the
+ * current tab.
+ */
+function close_tab(aTabToClose) {
+ // get the current tab count so we can make sure the tab actually opened.
+ let preCount = mc.tabmail.tabContainer.childNodes.length;
+
+ mc.tabmail.closeTab(aTabToClose);
+ wait_for_message_display_completion();
+
+ // check that the tab count decreased
+ if (mc.tabmail.tabContainer.childNodes.length != preCount - 1)
+ throw new Error("The tab never actually got closed!");
+}
+
+/**
+ * Clear the selection. I'm not sure how we're pretending we did that.
+ */
+function select_none() {
+ wait_for_message_display_completion();
+ mc.dbView.selection.clearSelection();
+ // give the event queue a chance to drain...
+ controller.sleep(0);
+}
+
+function _normalize_view_index(aViewIndex) {
+ if (aViewIndex < 0)
+ return mc.dbView.QueryInterface(Ci.nsITreeView).rowCount + aViewIndex;
+ return aViewIndex;
+}
+
+/**
+ * Pretend we are clicking on a row with our mouse.
+ *
+ * @param aViewIndex If >= 0, the view index provided, if < 0, a reference to
+ * a view index counting from the last row in the tree. -1 indicates the
+ * last message in the tree, -2 the second to last, etc.
+ *
+ * @return The message header selected.
+ */
+function select_click_row(aViewIndex) {
+ wait_for_message_display_completion();
+ aViewIndex = _normalize_view_index(aViewIndex);
+ // this should set the current index as well as setting the selection.
+ mc.dbView.selection.select(aViewIndex);
+ wait_for_message_display_completion(mc, true);
+ return mc.dbView.getMsgHdrAt(aViewIndex);
+}
+
+/**
+ * Pretend we are toggling the thread specified by a row.
+ *
+ * @param aViewIndex If >= 0, the view index provided, if < 0, a reference to
+ * a view index counting from the last row in the tree. -1 indicates the
+ * last message in the tree, -2 the second to last, etc.
+ *
+ */
+function toggle_thread_row(aViewIndex) {
+ wait_for_message_display_completion();
+ aViewIndex = _normalize_view_index(aViewIndex);
+ mc.dbView.toggleOpenState(aViewIndex);
+ wait_for_message_display_completion(mc, true);
+}
+
+
+/**
+ * Pretend we are clicking on a row with our mouse with the control key pressed,
+ * resulting in the addition/removal of just that row to/from the selection.
+ *
+ * @param aViewIndex If >= 0, the view index provided, if < 0, a reference to
+ * a view index counting from the last row in the tree. -1 indicates the
+ * last message in the tree, -2 the second to last, etc.
+ *
+ * @return The message header of the affected message.
+ */
+function select_control_click_row(aViewIndex) {
+ wait_for_message_display_completion();
+ aViewIndex = _normalize_view_index(aViewIndex);
+ // Control-clicking augments the selection and moves the current index. It
+ // also clears the shift pivot, but that's fine as it falls back to the
+ // current index if there is no shift pivot, which works for duplicating
+ // actual behavior.
+ mc.dbView.selection.rangedSelect(aViewIndex, aViewIndex, true);
+ mc.dbView.selection.currentIndex = aViewIndex;
+ // give the event queue a chance to drain...
+ controller.sleep(0);
+ wait_for_message_display_completion();
+ return mc.dbView.getMsgHdrAt(aViewIndex);
+}
+
+/**
+ * Pretend we are clicking on a row with our mouse with the shift key pressed,
+ * adding all the messages between the shift pivot and the shift selected row.
+ *
+ * @param aViewIndex If >= 0, the view index provided, if < 0, a reference to
+ * a view index counting from the last row in the tree. -1 indicates the
+ * last message in the tree, -2 the second to last, etc.
+ *
+ * @return The message headers for all messages that are now selected.
+ */
+function select_shift_click_row(aViewIndex) {
+ wait_for_message_display_completion();
+ aViewIndex = _normalize_view_index(aViewIndex);
+ // Passing -1 as the start range checks the shift-pivot, which should be -1,
+ // so it should fall over to the current index, which is what we want. It
+ // will then set the shift-pivot to the previously-current-index and update
+ // the current index to be what we shift-clicked on. All matches user
+ // interaction.
+ mc.dbView.selection.rangedSelect(-1, aViewIndex, false);
+ // give the event queue a chance to drain...
+ controller.sleep(0);
+ wait_for_message_display_completion();
+ return mc.folderDisplay.selectedMessages;
+}
+
+/**
+ * Helper function to click on a row with a given button.
+ */
+function _row_click_helper(aViewIndex, aButton) {
+ let treeBox = mc.threadTree.treeBoxObject;
+ // very important, gotta be able to see the row
+ treeBox.ensureRowIsVisible(aViewIndex);
+ // now figure out the coords
+ let children = mc.e("threadTree", {tagName: "treechildren"});
+ let x = children.boxObject.x;
+ let y = children.boxObject.y;
+ let rowX = 10;
+ let rowY = treeBox.rowHeight * (aViewIndex - treeBox.getFirstVisibleRow());
+ if (treeBox.getRowAt(x + rowX, y + rowY) != aViewIndex) {
+ throw new Error("Thought we would find row " + aViewIndex + " at " +
+ rowX + "," + rowY + " but we found " +
+ treeBox.getRowAt(rowX, rowY));
+ }
+ let tx = mc.threadTree.boxObject.x;
+ let ty = mc.threadTree.boxObject.y;
+ EventUtils.synthesizeMouse(mc.threadTree, x + rowX - tx, y + rowY - ty,
+ {type: "mousedown", button: aButton}, mc.window);
+ if (aButton == 2)
+ EventUtils.synthesizeMouse(mc.threadTree, x + rowX - tx, y + rowY - ty,
+ {type: "contextmenu", button: aButton},
+ mc.window);
+ EventUtils.synthesizeMouse(mc.threadTree, x + rowX - tx, y + rowY - ty,
+ {type: "mouseup", button: aButton}, mc.window);
+}
+
+/**
+ * Right-click on the tree-view in question. With any luck, this will have
+ * the side-effect of opening up a pop-up which it is then on _your_ head
+ * to do something with or close. However, we have helpful popup function
+ * helpers because I'm so nice.
+ *
+ * @return The message header that you clicked on.
+ */
+function right_click_on_row(aViewIndex) {
+ let msgHdr = mc.dbView.getMsgHdrAt(aViewIndex);
+ _row_click_helper(aViewIndex, 2);
+ return msgHdr;
+}
+
+/**
+ * Middle-click on the tree-view in question, presumably opening a new message
+ * tab.
+ *
+ * @return [The new tab, the message that you clicked on.]
+ */
+function middle_click_on_row(aViewIndex) {
+ let msgHdr = mc.dbView.getMsgHdrAt(aViewIndex);
+ _row_click_helper(aViewIndex, 1);
+ return [mc.tabmail.currentTabInfo, msgHdr];
+}
+
+/**
+ * Assuming the context popup is popped-up (via right_click_on_row), select
+ * the deletion option. If the popup is not popped up, you are out of luck.
+ */
+function delete_via_popup() {
+ plan_to_wait_for_folder_events("DeleteOrMoveMsgCompleted",
+ "DeleteOrMoveMsgFailed");
+ mc.click(mc.eid("mailContext-delete"));
+ // for reasons unknown, the pop-up does not close itself?
+ close_popup();
+ wait_for_folder_events();
+}
+
+/**
+ * Close the open pop-up.
+ */
+function close_popup(aController) {
+ if (aController === undefined)
+ aController = mc;
+ aController.keypress(aController.eid("mailContext"), "VK_ESCAPE", {});
+ // drain event queue
+ aController.sleep(0);
+}
+
+/**
+ * Pretend we are pressing the delete key, triggering message deletion of the
+ * selected messages.
+ *
+ * @param aController The controller in whose context to do this, defaults to
+ * |mc| if omitted.
+ */
+function press_delete(aController) {
+ if (aController === undefined)
+ aController = mc;
+ // if something is loading, make sure it finishes loading...
+ wait_for_message_display_completion(aController);
+ plan_to_wait_for_folder_events("DeleteOrMoveMsgCompleted",
+ "DeleteOrMoveMsgFailed");
+ aController.keypress(aController == mc ? mc.eThreadTree : null,
+ "VK_DELETE", {});
+ wait_for_folder_events();
+}
+
+/**
+ * Wait for the |folderDisplay| on aController (defaults to mc if omitted) to
+ * finish loading. This generally only matters for folders that have an active
+ * search.
+ * This method is generally called automatically most of the time, and you
+ * should not need to call it yourself unless you are operating outside the
+ * helper methods in this file.
+ */
+function wait_for_all_messages_to_load(aController) {
+ if (aController === undefined)
+ aController = mc;
+ if (aController.allMessagesLoaded) {
+ // even though we're taking the fast-path out, let's give the event loop a
+ // chance to drain.
+ aController.sleep(0);
+ return;
+ }
+ if(!controller.waitForEval('subject.allMessagesLoaded', NORMAL_TIMEOUT,
+ FAST_INTERVAL, aController.folderDisplay))
+ throw new Error("Messages never finished loading. Timed Out.");
+ // the above may return immediately, meaning the event queue might not get a
+ // chance. give it a chance now.
+ aController.sleep(0);
+}
+
+/**
+ * If a message is in the process of loading, let it finish. Otherwise we get
+ * horrible assertions like so:
+ * ###!!! ASSERTION: Overwriting an existing document channel!
+ *
+ * @param aController optional controller, defaulting to |mc|.
+ * @param aLoadDemanded optional indication that we expect and demand that a
+ * message be loaded. If you call us before the message loading is
+ * initiated, you will need to pass true for this so that we don't see
+ * that a load hasn't started and assume none is required. Defaults to
+ * false. This relies on aController.messageDisplay.messageLoaded to
+ * be reliable; make sure it is false when entering this function.
+ */
+function wait_for_message_display_completion(aController, aLoadDemanded) {
+ if (aController === undefined)
+ aController = mc;
+ let contentPane = aController.contentPane;
+ let oldHref = null;
+
+ // There are a couple possible states the universe can be in:
+ // 1) No message load happened or is going to happen.
+ // 2) The only message load that is going to happened has happened.
+ // 3) A message load is happening right now.
+ // 4) A message load should happen in the near future.
+ //
+ // We have nothing that needs to be done in cases 1 and 2. Case 3 is pretty
+ // easy for us. The question is differentiating between case 4 and (1, 2).
+ // We rely on MessageDisplayWidget.messageLoaded to differentiate this case
+ // for us.
+ let isLoadedChecker = function() {
+ // If a load is demanded, first require that MessageDisplayWidget think
+ // that the message is loaded. Because the notification is imperfect,
+ // this will strictly happen before the URL finishes running.
+ if (aLoadDemanded && !aController.messageDisplay.messageLoaded)
+ return false;
+
+ let docShell = contentPane.docShell;
+ if (!docShell)
+ return false;
+ let uri = docShell.currentURI;
+ // the URL will tell us if it is running, saves us from potential error
+ if (uri && (uri instanceof Components.interfaces.nsIMsgMailNewsUrl)) {
+ let urlRunningObj = {};
+ uri.GetUrlState(urlRunningObj);
+ // GetUrlState returns true if the url is still running
+ return !urlRunningObj.value;
+ }
+ // not a mailnews URL, just check the busy flags...
+ return !docShell.busyFlags;
+ };
+ controller.waitForEval('subject()',
+ NORMAL_TIMEOUT,
+ FAST_INTERVAL, isLoadedChecker);
+ // the above may return immediately, meaning the event queue might not get a
+ // chance. give it a chance now.
+ aController.sleep(0);
+}
+
+
+var FolderListener = {
+ _inited: false,
+ ensureInited: function() {
+ if (this._inited)
+ return;
+
+ let mailSession =
+ Cc["@mozilla.org/messenger/services/session;1"]
+ .getService(Ci.nsIMsgMailSession);
+ mailSession.AddFolderListener(this,
+ Ci.nsIFolderListener.event);
+
+ this._inited = true;
+ },
+
+ sawEvents: false,
+ watchingFor: null,
+ planToWaitFor: function FolderListener_planToWaitFor() {
+ this.sawEvents = false;
+ this.watchingFor = [];
+ for (let i = 0; i < arguments.length; i++)
+ this.watchingFor[i] = arguments[i];
+ },
+ waitForEvents: function FolderListener_waitForEvents() {
+ if (this.sawEvents)
+ return;
+ controller.waitForEval('subject.sawEvents', NORMAL_TIMEOUT,
+ FAST_INTERVAL, this);
+ },
+
+ OnItemEvent: function FolderNotificationHelper_OnItemEvent(
+ aFolder, aEvent) {
+ if (!this.watchingFor)
+ return;
+ if (this.watchingFor.indexOf(aEvent.toString()) != -1) {
+ this.watchingFor = null;
+ this.sawEvents = true;
+ }
+ },
+};
+
+/**
+ * Plan to wait for an nsIFolderListener.OnItemEvent matching one of the
+ * provided strings. Call this before you do the thing that triggers the
+ * event, then call |wait_for_folder_events| after the event. This ensures
+ * that we see the event, because it might be too late after you initiate
+ * the thing that would generate the event.
+ * For example, plan_to_wait_for_folder_events("DeleteOrMoveMsgCompleted",
+ * "DeleteOrMoveMsgFailed") waits for a deletion completion notification
+ * when you call |wait_for_folder_events|.
+ * The waiting is currently un-scoped, so the event happening on any folder
+ * triggers us. It is expected that you won't try and have multiple events
+ * in-flight or will augment us when the time comes to have to deal with that.
+ */
+function plan_to_wait_for_folder_events() {
+ FolderListener.ensureInited();
+ FolderListener.planToWaitFor.apply(FolderListener, arguments);
+}
+function wait_for_folder_events() {
+ FolderListener.waitForEvents();
+}
+
+/**
+ * Assert that the given synthetic message sets are present in the folder
+ * display.
+ *
+ * @param aSynSets Either a single SyntheticMessageSet or a list of them.
+ * @param aController Optional controller, which we get the folderDisplay
+ * property from. If omitted, we use the mc (mainController).
+ */
+function assert_messages_in_view(aSynSets, aController) {
+ if (aController === undefined)
+ aController = mc;
+ viewWrapperTestUtils.verify_messages_in_view(aSynSets,
+ aController.folderDisplay.view);
+}
+
+/**
+ * Assert the the given message/messages are not present in the view.
+ * @param aMessages Either a single nsIMsgDBHdr or a list of them.
+ */
+function assert_messages_not_in_view(aMessages, aController) {
+ if (aController === undefined)
+ aController = mc;
+ if (aMessages instanceof Ci.nsIMsgDBHdr)
+ aMessages = [aMessages];
+ for each (let [, msgHdr] in Iterator(aMessages)) {
+ if (mc.dbView.findIndexOfMsgHdr(msgHdr, true) != nsMsgViewIndex_None)
+ throw new Error("Message header is present in view but should not be: " +
+ msgHdr.mime2DecodedSubject + " index: " +
+ mc.dbView.findIndexOfMsgHdr(msgHdr, true));
+ }
+}
+var assert_message_not_in_view = assert_messages_not_in_view;
+
+/**
+ * Helper function for use by assert_selected / assert_selected_and_displayed /
+ * assert_displayed.
+ */
+function _process_row_message_arguments() {
+ let troller = mc;
+ // - normalize into desired selected view indices
+ let desiredIndices = [];
+ for (let iArg = 0; iArg < arguments.length; iArg++) {
+ let arg = arguments[iArg];
+ // An integer identifying a view index
+ if (typeof(arg) == "number") {
+ desiredIndices.push(_normalize_view_index(arg));
+ }
+ // A message header
+ else if (arg instanceof Ci.nsIMsgDBHdr) {
+ // do not expand; the thing should already be selected, eg expanded!
+ let viewIndex = troller.dbView.findIndexOfMsgHdr(arg, false);
+ if (viewIndex == nsMsgViewIndex_None)
+ throw_and_dump_view_state(
+ "Message not present in view that should be there. " +
+ "(" + arg.messageKey + ": " + arg.mime2DecodedSubject + ")");
+ desiredIndices.push(viewIndex);
+ }
+ // A list containing two integers, indicating a range of view indices.
+ else if (arg.length == 2 && typeof(arg[0]) == "number") {
+ let lowIndex = _normalize_view_index(arg[0]);
+ let highIndex = _normalize_view_index(arg[1]);
+ for (let viewIndex = lowIndex; viewIndex <= highIndex; viewIndex++)
+ desiredIndices.push(viewIndex);
+ }
+ // a List of message headers
+ else if (arg.length !== undefined) {
+ for (let iMsg = 0; iMsg < arg.length; iMsg++) {
+ let msgHdr = arg[iMsg].QueryInterface(Ci.nsIMsgDBHdr);
+ if (!msgHdr)
+ throw new Error(arg[iMsg] + " is not a message header!");
+ // false means do not expand, it should already be selected
+ let viewIndex = troller.dbView.findIndexOfMsgHdr(msgHdr, false);
+ if (viewIndex == nsMsgViewIndex_None)
+ throw new Error("Message not present in view that should be there.");
+ desiredIndices.push(viewIndex);
+ }
+ }
+ // it's a MozmillController
+ else if (arg.window) {
+ troller = arg;
+ }
+ else {
+ throw new Error("Illegal argument: " + arg);
+ }
+ }
+ // sort by integer value
+ desiredIndices.sort(function (a, b) { return a - b;} );
+
+ return [troller, desiredIndices];
+}
+
+/**
+ * Asserts that the given set of messages are selected. Unless you are dealing
+ * with transient selections resulting from right-clicks, you want to be using
+ * assert_selected_and_displayed because it makes sure that the display is
+ * correct too.
+ *
+ * The arguments consist of one or more of the following:
+ * - A MozmillController, indicating we should use that controller instead of
+ * the default, "mc" (corresponding to the 3pane.) Pass this first!
+ * - An integer identifying a view index.
+ * - A list containing two integers, indicating a range of view indices.
+ * - A message header.
+ * - A list of message headers.
+ */
+function assert_selected() {
+ let [troller, desiredIndices] =
+ _process_row_message_arguments.apply(this, arguments);
+
+ // - get the actual selection (already sorted by integer value)
+ let selectedIndices = troller.folderDisplay.selectedIndices;
+ // - test selection equivalence
+ // which is the same as string equivalence in this case. muah hah hah.
+ if (desiredIndices.toString() != selectedIndices.toString())
+ throw new Error("Desired selection is: " + desiredIndices + " but actual " +
+ "selection is: " + selectedIndices);
+
+ return [troller, desiredIndices];
+}
+
+
+/**
+ * Assert that the given set of messages is displayed, but not necessarily
+ * selected. Unless you are dealing with transient selection issues or some
+ * other situation where the FolderDisplay should not be correlated with the
+ * MessageDisplay, you really should be using assert_selected_and_displayed.
+ *
+ * The arguments consist of one or more of the following:
+ * - A MozmillController, indicating we should use that controller instead of
+ * the default, "mc" (corresponding to the 3pane.) Pass this first!
+ * - An integer identifying a view index.
+ * - A list containing two integers, indicating a range of view indices.
+ * - A message header.
+ * - A list of message headers.
+ */
+function assert_displayed() {
+ let [troller, desiredIndices] =
+ _process_row_message_arguments.apply(this, arguments);
+ _internal_assert_displayed(false, troller, desiredIndices);
+}
+
+/**
+ * Assert-that-the-display-is-right logic. We need an internal version so that
+ * we can know whether we can trust/assert that folderDisplay.selectedMessage
+ * agrees with messageDisplay, and also so that we don't have to re-compute
+ * troller and desiredIndices.
+ */
+function _internal_assert_displayed(trustSelection, troller, desiredIndices) {
+ // - verify that the right thing is being displayed.
+ // no selection means folder summary.
+ if (desiredIndices.length == 0) {
+ // folder summary is not landed yet, just verify there is no message.
+ if (troller.messageDisplay.displayedMessage != null)
+ throw new Error("Message display should not think it is displaying a " +
+ "message.");
+ // make sure the content pane is pointed at about:blank
+ if (troller.window.content.location.href != "about:blank") {
+ throw new Error("the content pane should be blank, but is showing: '" +
+ troller.window.content.location.href + "'");
+ }
+ }
+ // 1 means the message should be displayed
+ else if (desiredIndices.length == 1) {
+ // make sure message display thinks we are in single message display mode
+ if (!troller.messageDisplay.singleMessageDisplay)
+ throw new Error("Message display is not in single message display mode.");
+ // now make sure that we actually are in single message display mode
+ let singleMessagePane = troller.e("singlemessage");
+ let multiMessagePane = troller.e("multimessage");
+ if (singleMessagePane && singleMessagePane.hidden)
+ throw new Error("Single message pane is hidden but it should not be.");
+ if (multiMessagePane && !multiMessagePane.hidden)
+ throw new Error("Multiple message pane is visible but it should not be.");
+
+ if (trustSelection) {
+ if (troller.folderDisplay.selectedMessage !=
+ troller.messageDisplay.displayedMessage)
+ throw new Error("folderDisplay.selectedMessage != " +
+ "messageDisplay.displayedMessage! (fd: " +
+ troller.folderDisplay.selectedMessage + " vs md: " +
+ troller.messageDisplay.displayedMessage + ")");
+ }
+
+ let msgHdr = troller.messageDisplay.displayedMessage;
+ let msgUri = msgHdr.folder.getUriForMsg(msgHdr);
+ // wait for the document to load so that we don't try and replace it later
+ // and get that stupid assertion
+ wait_for_message_display_completion();
+ // make sure the content pane is pointed at the right thing
+
+ let msgService =
+ troller.folderDisplay.messenger.messageServiceFromURI(msgUri);
+ let msgUrlObj = {};
+ msgService.GetUrlForUri(msgUri, msgUrlObj, troller.folderDisplay.msgWindow);
+ let msgUrl = msgUrlObj.value;
+ if (troller.window.content.location.href != msgUrl.spec)
+ throw new Error("The content pane is not displaying the right message! " +
+ "Should be: " + msgUrl.spec + " but it's: " +
+ troller.window.content.location.href);
+ }
+ // multiple means some form of multi-message summary
+ else {
+ // XXX deal with the summarization threshold bail case.
+
+ // make sure the message display thinks we are in multi-message mode
+ if (troller.messageDisplay.singleMessageDisplay)
+ throw new Error("Message display should not be in single message display"+
+ "mode! Selected indices: " + selectedIndices);
+
+ // now make sure that we actually are in nultiple message display mode
+ let singleMessagePane = troller.e("singlemessage");
+ let multiMessagePane = troller.e("multimessage");
+ if (singleMessagePane && !singleMessagePane.hidden)
+ throw new Error("Single message pane is visible but it should not be.");
+ if (multiMessagePane && multiMessagePane.hidden)
+ throw new Error("Multiple message pane is hidden but it should not be.");
+
+ // and _now_ make sure that we actually summarized what we wanted to
+ // summarize.
+ let desiredMessages = [mc.dbView.getMsgHdrAt(vi) for each
+ ([, vi] in Iterator(desiredIndices))];
+ assert_selection_summarized(troller, desiredMessages);
+ }
+}
+
+/**
+ * Assert that the messages corresponding to the one or more message spec
+ * arguments are selected and displayed. If you specify multiple messages,
+ * we verify that the multi-message selection mode is in effect and that they
+ * are doing the desired thing. (Verifying the summarization may seem
+ * overkill, but it helps make the tests simpler and allows you to be more
+ * confident if you're just running one test that everything in the test is
+ * performing in a sane fashion. Refactoring could be in order, of course.)
+ *
+ * The arguments consist of one or more of the following:
+ * - A MozmillController, indicating we should use that controller instead of
+ * the default, "mc" (corresponding to the 3pane.) Pass this first!
+ * - An integer identifying a view index.
+ * - A list containing two integers, indicating a range of view indices.
+ * - A message header.
+ * - A list of message headers.
+ */
+function assert_selected_and_displayed() {
+ // make sure the selection is right first.
+ let [troller, desiredIndices] = assert_selected.apply(this, arguments);
+ // now make sure the display is right
+ _internal_assert_displayed(true, troller, desiredIndices);
+}
+
+/**
+ * @return true if |aSetOne| is equivalent to |aSetTwo| where the sets are
+ * really just lists of nsIMsgDBHdrs with cool names.
+ */
+function _verify_message_sets_equivalent(aSetOne, aSetTwo) {
+ let uniqy1 = [msgHdr.folder.URI + msgHdr.messageKey for each
+ ([, msgHdr] in Iterator(aSetOne))];
+ uniqy1.sort();
+ let uniqy2 = [msgHdr.folder.URI + msgHdr.messageKey for each
+ ([, msgHdr] in Iterator(aSetTwo))];
+ uniqy2.sort();
+ // stringified versions should now be equal...
+ return uniqy1.toString() == uniqy2.toString();
+}
+
+/**
+ * Asserts that the messages the controller's folder display widget thinks are
+ * summarized are in fact summarized. This is automatically called by
+ * assert_selected_and_displayed, so you do not need to call this directly
+ * unless you are testing the summarization logic.
+ *
+ * @param aController The controller who has the summarized display going on.
+ * @param aMessages Optional set of messages to verify. If not provided, this
+ * is extracted via the folderDisplay.
+ */
+function assert_selection_summarized(aController, aSelectedMessages) {
+ // - Compensate for selection stabilization code.
+ // Although test-window-helpers sets the stabilization interval to 0, we
+ // still need to make sure we have drained the event queue so that it has
+ // actually gotten a chance to run.
+ controller.sleep(0);
+
+ // - Verify summary object knows about right messages
+ if (aSelectedMessages == null)
+ aSelectedMessages = aController.folderDisplay.selectedMessages;
+
+ let summary = aController.window.gSummary;
+ if (aSelectedMessages.length != summary._msgHdrs.length) {
+ let elaboration = "Summary contains " + summary._msgHdrs.length +
+ " messages, expected " + aSelectedMessages.length + ".";
+ throw new Error("Summary does not contain the right set of messages. " +
+ elaboration);
+ }
+ if (!_verify_message_sets_equivalent(summary._msgHdrs, aSelectedMessages)) {
+ let elaboration = "Summary: " + summary._msgHdrs + " Selected: " +
+ aSelectedMessages + ".";
+ throw new Error("Summary does not contain the right set of messages. " +
+ elaboration);
+ }
+}
+
+/**
+ * Assert that there is nothing selected and, assuming we are in a folder, that
+ * the folder summary is displayed.
+ */
+let assert_nothing_selected = assert_selected_and_displayed;
+
+/**
+ * Assert that the given view index or message is visible in the thread pane.
+ */
+function assert_visible(aViewIndexOrMessage) {
+ let viewIndex;
+ if (typeof(aViewIndexOrMessage) == "number")
+ viewIndex = _normalize_view_index(aViewIndexOrMessage);
+ else
+ viewIndex = mc.dbView.findIndexOfMsgHdr(aViewIndexOrMessage);
+ let treeBox = mc.threadTree.boxObject.QueryInterface(Ci.nsITreeBoxObject);
+ if (viewIndex < treeBox.getFirstVisibleRow() ||
+ viewIndex > treeBox.getLastVisibleRow())
+ throw new Error("View index " + viewIndex + " is not visible! (" +
+ treeBox.getFirstVisibleRow() + "-" +
+ treeBox.getLastVisibleRow() + " are visible)");
+}
+
+/**
+ * Put the view in unthreaded mode.
+ */
+function make_display_unthreaded() {
+ wait_for_message_display_completion();
+ mc.folderDisplay.view.showUnthreaded = true;
+ // drain event queue
+ mc.sleep(0);
+}
+
+/**
+ * Put the view in threaded mode.
+ */
+function make_display_threaded() {
+ wait_for_message_display_completion();
+ mc.folderDisplay.view.showThreaded = true;
+ // drain event queue
+ mc.sleep(0);
+}
+
+/**
+ * Put the view in group-by-sort mode.
+ */
+function make_display_grouped() {
+ wait_for_message_display_completion();
+ mc.folderDisplay.view.showGroupedBySort = true;
+ // drain event queue
+ mc.sleep(0);
+}
+
+/**
+ * assert that the multimessage/thread summary view contains
+ * the specified number of elements of the specified class.
+ *
+ * @param aClassName: the class to use to select
+ * @param aNumElts: the number of expected elements that have that class
+ */
+
+function assert_summary_contains_N_divs(aClassName, aNumElts) {
+ let htmlframe = mc.e('multimessage');
+ let matches = htmlframe.contentDocument.getElementsByClassName(aClassName);
+ if (matches.length != aNumElts)
+ throw new Error("Expected to find " + aNumElts + " elements with class " +
+ aClassName + ", found: " + matches.length);
+}
+
+
+function throw_and_dump_view_state(aMessage, aController) {
+ if (aController == null)
+ aController = mc;
+
+ dump("******** " + aMessage + "\n");
+ viewWrapperTestUtils.dump_view_state(aController.folderDisplay.view);
+ throw new Error(aMessage);
+}
+
+/** exported from viewWrapperTestUtils */
+var make_new_sets_in_folders;
+var make_new_sets_in_folder;
+var add_sets_to_folders;
+
+/**
+ * Load a file in its own 'module'.
+ *
+ * @param aPath A path relative to the comm-central source path.
+ *
+ * @return An object that serves as the global scope for the loaded file.
+ */
+function load_via_src_path(aPath) {
+ let srcPath = os.abspath("../../../..",os.getFileForPath( __file__));
+ let fullPath = os.abspath(aPath, os.getFileForPath(srcPath));
+ return frame.loadFile(fullPath, undefined);
+}
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/shared-modules/test-window-helpers.js
@@ -0,0 +1,782 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cu = Components.utils;
+
+var mozmill = {};
+Cu.import('resource://mozmill/modules/mozmill.js', mozmill);
+var controller = {};
+Cu.import('resource://mozmill/modules/controller.js', controller);
+var elib = {};
+Cu.import('resource://mozmill/modules/elementslib.js', elib);
+var frame = {};
+Cu.import('resource://mozmill/modules/frame.js', frame);
+
+Cu.import('resource://app/modules/iteratorUtils.jsm');
+
+const MODULE_NAME = 'window-helpers';
+
+/**
+ * Timeout to use when waiting for the first window ever to load. This is
+ * long because we are basically waiting for the entire app startup process.
+ */
+const FIRST_WINDOW_EVER_TIMEOUT_MS = 30000;
+/**
+ * Interval to check if the window has shown up for the first window ever to
+ * load. The check interval is longer because it's less likely the window
+ * is going to show up quickly and there is a cost to the check.
+ */
+const FIRST_WINDOW_CHECK_INTERVAL_MS = 300;
+
+/**
+ * Timeout for opening a window.
+ */
+const WINDOW_OPEN_TIMEOUT_MS = 10000;
+/**
+ * Check interval for opening a window.
+ */
+const WINDOW_OPEN_CHECK_INTERVAL_MS = 100;
+
+/**
+ * Timeout for closing a window.
+ */
+const WINDOW_CLOSE_TIMEOUT_MS = 10000;
+/**
+ * Check interval for closing a window.
+ */
+const WINDOW_CLOSE_CHECK_INTERVAL_MS = 100;
+
+function setupModule() {
+ // do nothing
+}
+
+function installInto(module) {
+ module.plan_for_new_window = plan_for_new_window;
+ module.wait_for_new_window = wait_for_new_window;
+ module.plan_for_modal_dialog = plan_for_modal_dialog;
+ module.wait_for_modal_dialog = wait_for_modal_dialog;
+ module.plan_for_window_close = plan_for_window_close;
+ module.wait_for_window_close = wait_for_window_close;
+ module.wait_for_existing_window = wait_for_existing_window;
+
+ module.augment_controller = augment_controller;
+}
+
+var WindowWatcher = {
+ _inited: false,
+ _firstWindowOpened: false,
+ ensureInited: function WindowWatcher_ensureInited() {
+ if (this._inited)
+ return;
+
+ // Add ourselves as an nsIWindowMediatorListener so we can here about when
+ // windows get registered with the window mediator. Because this
+ // generally happens
+ // Another possible means of getting this info would be to observe
+ // "xul-window-visible", but it provides no context and may still require
+ // polling anyways.
+ mozmill.wm.addListener(this);
+
+ this._inited = true;
+ },
+
+ /**
+ * Track the windowtypes we are waiting on. Keys are windowtypes. When
+ * watching for new windows, values are initially null, and are set to an
+ * nsIXULWindow when we actually find the window. When watching for closing
+ * windows, values are nsIXULWindows. This symmetry lets us have windows
+ * that appear and dis-appear do so without dangerously confusing us (as
+ * long as another one comes along...)
+ */
+ waitingList: {},
+ /**
+ * Note that we will be looking for a window with the given window type
+ * (ex: "mailnews:search"). This allows us to be ready if an event shows
+ * up before waitForWindow is called.
+ */
+ planForWindowOpen: function WindowWatcher_planForWindowOpen(aWindowType) {
+ this.waitingList[aWindowType] = null;
+ },
+
+ /**
+ * Like planForWindowOpen but we check for already-existing windows.
+ */
+ planForAlreadyOpenWindow:
+ function WindowWatcher_planForAlreadyOpenWindow(aWindowType) {
+ this.waitingList[aWindowType] = null;
+ // We need to iterate over all the XUL windows and consider them all.
+ // We can't pass the window type because the window might not have a
+ // window type yet.
+ // because this iterates from old to new, this does the right thing in that
+ // side-effects of consider will pick the most recent window.
+ for each (let xulWindow in fixIterator(
+ mozmill.wm.getXULWindowEnumerator(null),
+ Ci.nsIXULWindow)) {
+ if (!this.consider(xulWindow))
+ this.monitoringList.push(xulWindow);
+ }
+ },
+
+ /**
+ * The current windowType we are waiting to open. This is mainly a means of
+ * communicating the desired window type to monitorize without having to
+ * put the argument in the eval string.
+ */
+ waitingForOpen: null,
+ /**
+ * Wait for the given windowType to open and finish loading.
+ *
+ * @return The window wrapped in a MozMillController.
+ */
+ waitForWindowOpen: function WindowWatcher_waitForWindowOpen(aWindowType) {
+ this.waitingForOpen = aWindowType;
+ controller.waitForEval(
+ 'subject.monitorizeOpen()',
+ this._firstWindowOpened ? WINDOW_OPEN_TIMEOUT_MS
+ : FIRST_WINDOW_EVER_TIMEOUT_MS,
+ this._firstWindowOpened ? WINDOW_OPEN_CHECK_INTERVAL_MS
+ : FIRST_WINDOW_CHECK_INTERVAL_MS,
+ this);
+ this.waitingForOpen = null;
+ let xulWindow = this.waitingList[aWindowType];
+dump("### XUL window: " + xulWindow + "\n");
+ let domWindow = xulWindow.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowInternal);
+dump("domWindow: " + domWindow + "\n");
+ delete this.waitingList[aWindowType];
+ // spin the event loop to make sure any setTimeout 0 calls have gotten their
+ // time in the sun.
+ controller.sleep(0);
+ this._firstWindowOpened = true;
+ return new controller.MozMillController(domWindow);
+ },
+
+ /**
+ * Because the modal dialog spins its own event loop, the mozmill idiom of
+ * spinning your own event-loop as performed by waitForEval is no good. We
+ * use this timer to generate our events so that we can have a waitForEval
+ * equivalent.
+ *
+ * We only have one timer right now because modal dialogs that spawn modal
+ * dialogs are not tremendously likely.
+ */
+ _timer: null,
+ _timerRuntimeSoFar: 0,
+ /**
+ * The test function to run when the modal dialog opens.
+ */
+ subTestFunc: null,
+ planForModalDialog: function WindowWatcher_planForModalDialog(aWindowType,
+ aSubTestFunc) {
+ if (this._timer == null)
+ this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this.waitingForOpen = aWindowType;
+ this.subTestFunc = aSubTestFunc;
+ this.waitingList[aWindowType] = null;
+
+ this._timerRuntimeSoFar = 0;
+ this._timer.initWithCallback(this, WINDOW_OPEN_CHECK_INTERVAL_MS,
+ Ci.nsITimer.TYPE_REPEATING_SLACK);
+ },
+
+ /**
+ * This is the nsITimer notification we receive...
+ */
+ notify: function WindowWatcher_notify() {
+dump("Timer check!\n");
+ if (this.monitorizeOpen()) {
+ // okay, the window is opened, and we should be in its event loop now.
+dump(" THIS IS IT!\n");
+ let xulWindow = this.waitingList[this.waitingForOpen];
+dump(" xul window: " + xulWindow + "\n");
+ let domWindow = xulWindow.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowInternal);
+dump(" dom window: " + domWindow + "\n");
+ let troller = new controller.MozMillController(domWindow);
+ augment_controller(troller, this.waitingForOpen);
+
+dump(" cleanup!\n");
+ delete this.waitingList[this.waitingForOpen];
+ this._timer.cancel();
+dump("canceled!\n");
+ try {
+ dump("::: calling\n");
+ try {
+ let runner = new frame.Runner(collector);
+ runner.wrapper(this.subTestFunc, troller);
+ }
+ catch (ex) {
+ dump("problem running: " + ex.fileName + ":" + ex.lineNumber + ": " + ex + "\n");
+ }
+ dump("::: called\n");
+ }
+ finally {
+ this.subTestFunc = null;
+ }
+ // now we are waiting for it to close...
+ this.waitingForClose = this.waitingForOpen;
+ this.waitingForOpen = null;
+
+ // if the test failed, make sure we force the window closed...
+ // except I'm not sure how to easily figure that out...
+ // so just close it no matter what.
+ troller.window.close();
+ }
+ // notify is only used for modal dialogs, which are never the first window,
+ // so we can always just use this set of timeouts/intervals.
+ this._timerRuntimeSoFar += WINDOW_OPEN_CHECK_INTERVAL_MS;
+ if (this._timerRuntimeSoFar >= WINDOW_OPEN_TIMEOUT_MS) {
+ dump("!!! TIMEOUT WHILE WAITING FOR MODAL DIALOG !!!\n");
+ this._timer.cancel();
+ throw new Error("Timeout while waiting for modal dialog.\n");
+ }
+ },
+
+ /**
+ * Symmetry for planForModalDialog; conceptually provides the waiting. In
+ * reality, all we do is potentially soak up the event loop a little to
+ */
+ waitForModalDialog: function WindowWatcher_waitForModalDialog(aWindowType) {
+ // did the window already come and go?
+ if (this.subTestFunc == null)
+ return;
+ // spin the event loop until we the window has come and gone.
+ controller.waitForEval(
+ 'subject.waitingForOpen == null && subject.waitingForClose == null',
+ WINDOW_OPEN_TIMEOUT_MS, WINDOW_OPEN_CHECK_INTERVAL_MS, this);
+ this.waitingForClose = null;
+ },
+
+ planForWindowClose: function WindowWatcher_planForWindowClose(aXULWindow) {
+ let windowType =
+ aXULWindow.document.documentElement.getAttribute("windowtype");
+ this.waitingList[windowType] = aXULWindow;
+ this.waitingForClose = windowType;
+ },
+
+ /**
+ * The current windowType we are waiting to close. Same deal as
+ * waitingForOpen, this makes the eval less crazy.
+ */
+ waitingForClose: null,
+ waitForWindowClose: function WindowWatcher_waitForWindowClose() {
+ controller.waitForEval('subject.monitorizeClose()',
+ WINDOW_CLOSE_TIMEOUT_MS,
+ WINDOW_CLOSE_CHECK_INTERVAL_MS, this);
+ let didDisappear = this.waitingList[this.waitingForClose] == null;
+ delete this.waitingList[windowType];
+ let windowType = this.waitingForClose;
+ this.waitingForClose = null;
+ if (!didDisappear)
+ throw new Error(windowType + " window did not disappear!");
+ },
+
+ /**
+ * This notification gets called when windows tell the widnow mediator when
+ * the window title gets changed. In theory, we could use this to be
+ * event driven with less polling (effort), but it is not to be.
+ */
+ onWindowTitleChange: function WindowWatcher_onWindowTitleChange(
+ aXULWindow, aNewTitle) {
+ },
+
+ /**
+ * Used by waitForWindowOpen to check all of the windows we are monitoring and
+ * then check if we have any results.
+ *
+ * @return true if we found what we were |waitingForOpen|, false otherwise.
+ */
+ monitorizeOpen: function () {
+ for (let iWin = this.monitoringList.length - 1; iWin >= 0; iWin--) {
+ let xulWindow = this.monitoringList[iWin];
+ if (this.consider(xulWindow))
+ this.monitoringList.splice(iWin, 1);
+ }
+
+ return this.waitingList[this.waitingForOpen] != null;
+ },
+
+ /**
+ * Used by waitForWindowClose to check if the window we are waiting to close
+ * actually closed yet.
+ *
+ * @return true if it closed.
+ */
+ monitorizeClose: function () {
+ return this.waitingList[this.waitingForClose] == null;
+ },
+
+ /**
+ * A list of xul windows to monitor because they are loading and it's not yet
+ * possible to tell whether they are something we are looking for.
+ */
+ monitoringList: [],
+ /**
+ * Monitor the given window's loading process until we can determine whether
+ * it is what we are looking for.
+ */
+ monitorWindowLoad: function(aXULWindow) {
+ this.monitoringList.push(aXULWindow);
+ },
+
+ /**
+ * nsIWindowMediatorListener notification that a XUL window was opened. We
+ * check out the window, and if we were not able to fully consider it, we
+ * add it to our monitoring list.
+ */
+ onOpenWindow: function WindowWatcher_onOpenWindow(aXULWindow) {
+ if (!this.consider(aXULWindow))
+ this.monitorWindowLoad(aXULWindow);
+ },
+
+ /**
+ * Consider if the given window is something in our |waitingList|.
+ *
+ * @return true if we were able to fully consider the object, false if we were
+ * not and need to be called again on the window later. This has no
+ * relation to whether the window was one in our waitingList or not.
+ * Check the waitingList structure for that.
+ */
+ consider: function (aXULWindow) {
+dump("### considering: " + aXULWindow + "\n");
+ let docshell = aXULWindow.docShell;
+ // we need the docshell to exist...
+ if (!docshell)
+ return false;
+dump("### has docshell\n");
+ // we can't know if it's the right document until it's not busy
+ if (docshell.busyFlags)
+ return false;
+dump("### not busy\n");
+ // it also needs to have content loaded (it starts out not busy with no
+ // content viewer.)
+ if (docshell.contentViewer == null)
+ return false;
+dump("### has contentViewer\n");
+ // now we're cooking! let's get the document...
+ let outerDoc = docshell.contentViewer.DOMDocument;
+ // and make sure it's not blank. that's also an intermediate state.
+ if (outerDoc.location.href == "about:blank")
+ return false;
+dump("has href: " + outerDoc.location.href + "\n");
+ // finally, we can now have a windowtype!
+ let windowType = outerDoc.documentElement.getAttribute("windowtype");
+dump("has windowtype: " + windowType + "\n");
+dump("this: " + this + "\n");
+dump("waitingList: " + this.waitingList + "\n");
+ // stash the window if we were watching for it
+ if (windowType in this.waitingList) {
+ dump("It's there! setting...\n");
+ this.waitingList[windowType] = aXULWindow;
+ }
+ else {
+ dump("Not there! :( SCREWED\n");
+ }
+
+ return true;
+ },
+
+ /**
+ * Closing windows have the advantage of having to already have been loaded,
+ * so things like their windowtype are immediately available.
+ */
+ onCloseWindow: function WindowWatcher_onCloseWindow(aXULWindow) {
+ dump("!!! CLOSE EVENT: " + aXULWindow + "\n");
+ let domWindow = aXULWindow.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowInternal);
+ let windowType =
+ domWindow.document.documentElement.getAttribute("windowtype");
+ // XXX because of how we dance with things, equivalence is not gonna
+ // happen for us. This is most pragmatic.
+ if (this.waitingList[windowType] !== null)
+ this.waitingList[windowType] = null;
+ dump("close end proc\n");
+ },
+};
+
+/**
+ * Call this if the window you want to get may already be open. What we
+ * provide above just directly grabbing the window yourself is:
+ * - We wait for it to finish loading.
+ * - We augment it via the augment_controller mechanism.
+ *
+ * @param aWindowType the window type that will be created. This is literally
+ * the value of the "windowtype" attribute on the window. The values tend
+ * to look like "app:windowname", for example "mailnews:search".
+ *
+ * @return The loaded window of the given type wrapped in a MozmillController
+ * that is augmented using augment_controller.
+ */
+function wait_for_existing_window(aWindowType) {
+ WindowWatcher.ensureInited();
+ WindowWatcher.planForAlreadyOpenWindow(aWindowType);
+ return augment_controller(WindowWatcher.waitForWindowOpen(aWindowType),
+ aWindowType);
+}
+
+/**
+ * Call this just before you trigger the event that will cause a window to be
+ * displayed.
+ * In theory, we don't need this and could just do a sweep of existing windows
+ * when you call wait_for_new_window, or we could always just keep track of
+ * the most recently seen window of each type, but this is arguably more
+ * resilient in the face of multiple windows of the same type as long as you
+ * don't try and open them all at the same time.
+ *
+ * @param aWindowType the window type that will be created. This is literally
+ * the value of the "windowtype" attribute on the window. The values tend
+ * to look like "app:windowname", for example "mailnews:search".
+ */
+function plan_for_new_window(aWindowType) {
+ WindowWatcher.ensureInited();
+ WindowWatcher.planForWindowOpen(aWindowType);
+}
+
+
+/**
+ * Wait for the loading of the given window type to complete (that you
+ * previously told us about via |plan_for_new_window|), returning it wrapped
+ * in a MozmillController.
+ *
+ * @return The loaded window of the given type wrapped in a MozmillController
+ * that is augmented using augment_controller.
+ */
+function wait_for_new_window(aWindowType) {
+ return augment_controller(WindowWatcher.waitForWindowOpen(aWindowType),
+ aWindowType);
+}
+
+/**
+ * Plan for the imminent display of a modal dialog. Modal dialogs spin their
+ * own event loop which means that either that control flow will not return
+ * to the caller until the modal dialog finishes running. This means that
+ * you need to provide a sub-test function to be run inside the modal dialog
+ * (and it should not start with "test" or mozmill will also try and run it.)
+ *
+ * @param aWindowType The window type that you expect the modal dialog to have.
+ * @param aSubTestFunction The sub-test function that will be run once the modal
+ * dialog appears and is loaded. This function should take one argument,
+ * a MozmillController against the modal dialog.
+ */
+function plan_for_modal_dialog(aWindowType, aSubTestFunction) {
+ WindowWatcher.ensureInited();
+ WindowWatcher.planForModalDialog(aWindowType, aSubTestFunction);
+}
+function wait_for_modal_dialog(aWindowType) {
+ WindowWatcher.waitForModalDialog(aWindowType);
+}
+
+/**
+ * Call this just before you trigger the event that will cause the provided
+ * controller's window to disappear. You then follow this with a call to
+ * |wait_for_window_close| when you want to block on verifying the close.
+ *
+ * @param aController The MozmillController, potentially returned from a call to
+ * wait_for_new_window, whose window should be disappearing.
+ */
+function plan_for_window_close(aController) {
+ WindowWatcher.ensureInited();
+ WindowWatcher.planForWindowClose(aController.window);
+}
+
+/**
+ * Wait for the closure of the window you noted you would listen for its close
+ * in plan_for_window_close.
+ */
+function wait_for_window_close() {
+ WindowWatcher.waitForWindowClose();
+}
+
+/**
+ * Methods to augment every controller that passes through augment_controller.
+ */
+var AugmentEverybodyWith = {
+ methods: {
+ /**
+ * @param aId The element id to use to locate the (initial) element.
+ * @param aQuery Optional query to pick a child of the element identified
+ * by the id. Terms that can be used (and applied in this order):
+ * - tagName: Find children with the tagname, if further constraints don't
+ * whittle it down, the first element is chosen.
+ * - label: Whittle previous elements by their label.
+ *
+ * example:
+ * // find the child of bob that is a button with a "+" on it.
+ * e("bob", {tagName: "button", label: "+"});
+ * // example:
+ * e("threadTree", {tagName: "treechildren"});
+ *
+ * @return the element with the given id on the window's document
+ */
+ e: function _get_element_by_id_helper(aId, aQuery) {
+ let elem = this.window.document.getElementById(aId);
+ if (aQuery) {
+ if (aQuery.tagName) {
+ let elems = Array.slice.call(
+ elem.getElementsByTagName(aQuery.tagName));
+ if (aQuery.label)
+ elems = [elem for each (elem in elems)
+ if (elem.label == aQuery.label)];
+ elem = elems[0];
+ }
+ }
+ return elem;
+ },
+
+ /**
+ * @return an elementlib.Elem for the element with the given id on the
+ * window's document.
+ */
+ eid: function _get_elementid_by_id_helper(aId, aQuery) {
+ return new elib.Elem(this.e(aId, aQuery));
+ },
+
+ /**
+ * Find an element in the anonymous subtree of an element in the document
+ * identified by its id. You would use this to dig into XBL bindings that
+ * are not doing what you want. For example, jerks that don't focus right.
+ *
+ * Examples:
+ * // by class of the node
+ * a("searchVal0", {class: "search-value-textbox"});
+ * // when the thing is vaguely deck-like
+ * a("searchVal0", {crazyDeck: 0});
+ * // when you want the first descendent with the given tagName
+ * a("threadTree", {tagName: treechildren})
+ *
+ * @return the anonymous element determined by the query found in the
+ * anonymous sub-tree of the element with the given id.
+ */
+ a: function _get_anon_element_by_id_and_query(aId, aQuery) {
+ let realElem = this.window.document.getElementById(aId);
+ if (aQuery["class"]) {
+ return this.window.document.getAnonymousElementByAttribute(
+ realElem, "class", aQuery["class"]);
+ }
+ else if(aQuery.crazyDeck != null) {
+ let anonNodes = this.window.document.getAnonymousNodes(realElem);
+ let index;
+ if (realElem.hasAttribute("selectedIndex"))
+ index = parseInt(realElem.getAttribute("selectedIndex"));
+ else
+ index = aQuery.crazyDeck;
+ let elem = anonNodes[index];
+ return elem;
+ }
+ else if(aQuery.tagName) {
+ let anonNodes = this.window.document.getAnonymousNodes(realElem);
+ let index;
+ for (let iNode = 0; iNode < anonNodes.length; iNode++) {
+ let node = anonNodes[iNode];
+ let named = node.getElementsByTagName(aQuery.tagName);
+ if (named.length)
+ return named[0];
+ }
+ }
+ else {
+ let msg = "Query constraint not implemented, query contained:";
+ for (let [key, val] in Iterator(aQuery)) {
+ msg += " '" + key + "': " + val;
+ }
+ throw new Error(msg);
+ }
+ return null;
+ },
+ /**
+ * Wraps a call to a() in an elib.Elem.
+ */
+ aid: function _get_anon_elementid(aId, aQuery) {
+ return new elib.Elem(this.a(aId, aQuery));
+ },
+ },
+};
+
+/**
+ * Per-windowtype augmentations. Please use the documentation and general
+ * example of mail:3pane as your example.
+ */
+var PerWindowTypeAugmentations = {
+ /**
+ * The 3pane window is messenger.xul, the default window.
+ */
+ "mail:3pane": {
+ /**
+ * DOM elements to expose as attributes (by copying at augmentation time.)
+ */
+ elementsToExpose: {
+ threadTree: "threadTree",
+ tabmail: "tabmail",
+ },
+ /**
+ * DOM elements to expose as elementslib.IDs as attributes (at augmentation
+ * time.)
+ */
+ elementIDsToExpose: {
+ eThreadTree: "threadTree",
+ },
+ /**
+ * Globals from the controller's windows global scope at augmentation time.
+ */
+ globalsToExposeAtStartup: {
+ folderTreeView: "gFolderTreeView",
+ },
+ /**
+ * Globals from the controller's windows global to retrieve on-demand
+ * through getters.
+ */
+ globalsToExposeViaGetters: {
+ // all of these dudes
+ folderDisplay: "gFolderDisplay",
+ messageDisplay: "gMessageDisplay",
+ },
+ /**
+ * Custom getters whose |this| is the controller.
+ */
+ getters: {
+ dbView: function () {
+ return this.threadTree.view.QueryInterface(Ci.nsIMsgDBView);
+ },
+ contentPane: function () {
+ return this.tabmail.getBrowserForSelectedTab();
+ },
+ },
+
+ /**
+ * Invoked when we are augmenting a controller. This is a great time to
+ * poke into the global namespace as required.
+ */
+ onAugment: function(aController) {
+ // -- turn off summarization's stabilization logic for now by setting the
+ // timer interval to 0. We do need to make sure that we drain the event
+ // queue after performing anything that will summarize, but use of
+ // assert_selected_and_displayed in test-folder-display-helpers should
+ // handle that.
+ aController.window.MessageDisplayWidget.prototype
+ .SUMMARIZATION_SELECTION_STABILITY_INTERVAL_MS = 0;
+ }
+ },
+
+ /**
+ * Standalone message window.
+ */
+ "mail:messageWindow": {
+ elementsToExpose: {
+ contentPane: "messagepane",
+ },
+ // the load is deferred, so use a getter.
+ globalsToExposeViaGetters: {
+ folderDisplay: "gFolderDisplay",
+ messageDisplay: "gMessageDisplay",
+ },
+ getters: {
+ dbView: function () {
+ return this.folderDisplay.view.dbView;
+ },
+ },
+ },
+
+ /**
+ * The search window, via control-shift-F.
+ */
+ "mailnews:search": {
+ globalsToExposeAtStartup: {
+ folderDisplay: "gFolderDisplay",
+ }
+ }
+};
+
+function _augment_helper(aController, aAugmentDef) {
+ if (aAugmentDef.elementsToExpose) {
+ for each (let [key, value] in Iterator(aAugmentDef.elementsToExpose)) {
+ aController[key] = aController.window.document.getElementById(value);
+ }
+ }
+ if (aAugmentDef.elementsIDsToExpose) {
+ for each (let [key, value] in Iterator(aAugmentDef.elementIDsToExpose)) {
+ aController[key] = new elib.ID(
+ aController.window.document, value);
+ }
+ }
+ if (aAugmentDef.globalsToExposeAtStartup) {
+ for each (let [key, value] in
+ Iterator(aAugmentDef.globalsToExposeAtStartup)) {
+ aController[key] = aController.window[value];
+ }
+ }
+ if (aAugmentDef.globalsToExposeViaGetters) {
+ for each (let [key, value] in
+ Iterator(aAugmentDef.globalsToExposeViaGetters)) {
+ let globalName = value;
+ aController.__defineGetter__(key, function() {
+ return this.window[globalName];
+ });
+ }
+ }
+ if (aAugmentDef.getters) {
+ for each (let [key, value] in Iterator(aAugmentDef.getters)) {
+ aController.__defineGetter__(key, value);
+ }
+ }
+ if (aAugmentDef.methods) {
+ for each (let [key, value] in Iterator(aAugmentDef.methods)) {
+ aController[key] = value;
+ }
+ }
+
+ if (aAugmentDef.onAugment) {
+ aAugmentDef.onAugment(aController);
+ }
+}
+
+/**
+ * controller.js in mozmill actually has its own extension mechanism,
+ * controllerAdditions. Unfortunately, it does not make its stuff public at
+ * this time. In the future we can change ourselves to just use that
+ * mechanism.
+ */
+function augment_controller(aController, aWindowType) {
+ if (aWindowType === undefined)
+ aWindowType =
+ aController.window.document.documentElement.getAttribute("windowtype");
+
+ _augment_helper(aController, AugmentEverybodyWith);
+ if (PerWindowTypeAugmentations[aWindowType])
+ _augment_helper(aController, PerWindowTypeAugmentations[aWindowType]);
+ return aController;
+}
\ No newline at end of file
--- a/mail/themes/gnomestripe/jar.mn
+++ b/mail/themes/gnomestripe/jar.mn
@@ -1,12 +1,9 @@
classic.jar:
-+ skin/classic/global/tree.css (mail/tree.css)
-+ skin/classic/global/tree/sort-asc.gif (mail/icons/sort-asc.gif)
-+ skin/classic/global/tree/sort-dsc.gif (mail/icons/sort-dsc.gif)
skin/classic/communicator/smileys.css (mail/smileys.css)
% skin messenger classic/1.0 %skin/classic/messenger/
skin/classic/messenger/primaryToolbar.css (mail/primaryToolbar.css)
skin/classic/messenger/accountCentral.css (mail/accountCentral.css)
skin/classic/messenger/accountCreation.css (mail/accountCreation.css)
skin/classic/messenger/accountManage.css (mail/accountManage.css)
skin/classic/messenger/accountWizard.css (mail/accountWizard.css)
skin/classic/messenger/messageHeader.css (mail/messageHeader.css)
@@ -194,17 +191,16 @@ classic.jar:
skin/classic/messenger/icons/arrow/arrow-left.png (mail/icons/arrow/arrow-left.png)
skin/classic/messenger/icons/arrow/arrow-right.png (mail/icons/arrow/arrow-right.png)
skin/classic/messenger/icons/arrow/arrow-up.png (mail/icons/arrow/arrow-up.png)
skin/classic/messenger/icons/arrow/arrow-down.png (mail/icons/arrow/arrow-down.png)
skin/classic/messenger/icons/arrow/arrow-left-dim.png (mail/icons/arrow/arrow-left-dim.png)
skin/classic/messenger/icons/arrow/arrow-right-dim.png (mail/icons/arrow/arrow-right-dim.png)
skin/classic/messenger/icons/arrow/arrow-up-dim.png (mail/icons/arrow/arrow-up-dim.png)
skin/classic/messenger/icons/arrow/arrow-down-dim.png (mail/icons/arrow/arrow-down-dim.png)
- skin/classic/messenger/icons/chevron.png (mail/icons/chevron.png)
skin/classic/messenger/tagbg.png (mail/tagbg.png)
icon.png (mail/icon.png)
preview.png (mail/preview.png)
% skin editor classic/1.0 %skin/classic/editor/
skin/classic/editor/editor.css (editor/editor.css)
skin/classic/editor/EditorDialog.css (editor/EditorDialog.css)
skin/classic/editor/icons/img-align-bottom.gif (editor/img-align-bottom.gif)
skin/classic/editor/icons/img-align-left.gif (editor/img-align-left.gif)
deleted file mode 100644
index 246d5ee8bd00d75dfe1aecb18a70a4c898b4f2b7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100755
index b341abde70d4854bce80a79007ebde65e6b21c04..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100755
index bfeb6a8a8ae42839b54de9a4645e56826a1a1bf7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/mail/themes/gnomestripe/mail/messageBody.css
+++ b/mail/themes/gnomestripe/mail/messageBody.css
@@ -1,163 +1,170 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Joe Hewitt <hewitt@netscape.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either of the GNU General Public License Version 2 or later (the "GPL"),
- * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-/* ===== messageBody.css =================================================
- == Styles for the body of a mail message.
- ======================================================================= */
-
-@import url(chrome://communicator/skin/smileys.css);
-@import url(chrome://messenger/skin/messageQuotes.css);
-
-@namespace url("http://www.w3.org/1999/xhtml");
-
-mailattachcount {
- display: none;
-}
-
-/* :::: message header ::::: */
-
-.header-part1 {
- background-color: #EFEFEF;
-}
-
-.header-part2,
-.header-part3 {
- background-color: #DEDEDE;
-}
-
-div.headerdisplayname {
- display: inline;
- font-weight: bold;
- white-space: pre;
-}
-
-/* ::::: message text, incl. quotes ::::: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Joe Hewitt <hewitt@netscape.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/* ===== messageBody.css =================================================
+ == Styles for the body of a mail message.
+ ======================================================================= */
+
+@import url(chrome://communicator/skin/smileys.css);
+@import url(chrome://messenger/skin/messageQuotes.css);
+
+@namespace url("http://www.w3.org/1999/xhtml");
+
+mailattachcount {
+ display: none;
+}
+
+/* :::: message header ::::: */
+
+.header-part1 {
+ background-color: #EFEFEF;
+}
+
+.header-part2,
+.header-part3 {
+ background-color: #DEDEDE;
+}
+
+div.headerdisplayname {
+ display: inline;
+ font-weight: bold;
+ white-space: pre;
+}
+
+/* ::::: message text, incl. quotes ::::: */
a {
color: rgb(32,74,135); /* Sky Blue 3 */
}
-
-.moz-text-flowed blockquote,
-.moz-text-plain blockquote {
- margin: 0;
-}
-
-.moz-text-plain pre {
- margin: 0;
- font-family: inherit;
-}
-
-.moz-text-plain[wrap="true"] {
- white-space: pre-wrap;
-}
-
-.moz-text-plain[wrap="false"] {
- white-space: pre;
-}
-
-.moz-text-plain[wrap="flow"] .moz-txt-sig {
- white-space: pre-wrap;
-}
-
-.moz-text-plain[graphical-quote="false"] blockquote {
- border-style: none;
- padding: 0;
-}
-
-.moz-text-plain[graphical-quote="true"] .moz-txt-citetags {
- display: none;
-}
-
-span.moz-txt-underscore {
- text-decoration: underline;
-}
-
-span.moz-txt-formfeed {
- display: block;
- height: 100%;
-}
-
-/* ::::: attached images ::::: */
-.moz-attached-image[overflowing="true"] {
- cursor: -moz-zoom-out;
-}
-
-.moz-attached-image[isshrunk="true"] {
- cursor: -moz-zoom-in;
- max-width: 100%;
-}
-
-
-/* ::::: vcard ::::: */
-
-.moz-vcard-table {
- -moz-border-radius: 8px;
- border: thin solid gray;
- margin-top: 10px;
-}
-
-.moz-vcard-property {
- font-size: 80%;
- color: gray;
-}
-
-.moz-vcard-title-property {
-}
-
-.moz-vcard-badge {
- height: 24px;
- width: 24px;
- background-color: transparent;
- display: block;
- background-image: url("chrome://messenger/skin/addressbook/icons/abcard-large.png");
-}
-
-.moz-vcard-badge:hover {
- -moz-image-region: rect(30px 30px 60px 0px);
-}
-
-.moz-vcard-badge:focus {
- outline: none;
-}
-
-/* Correct style for messages already converted from RSS to HTML email;
- see bug 363154. */
-
-#\_mailrssiframe {
- width: 100%;
- height: 100%;
-}
+
+.moz-text-flowed blockquote,
+.moz-text-plain blockquote {
+ margin: 0;
+}
+
+.moz-text-plain pre {
+ margin: 0;
+ font-family: inherit;
+}
+
+.moz-text-plain[wrap="true"] {
+ white-space: pre-wrap;
+}
+
+.moz-text-plain[wrap="false"] {
+ white-space: pre;
+}
+
+.moz-text-plain[wrap="flow"] .moz-txt-sig {
+ white-space: pre-wrap;
+}
+
+.moz-text-plain[graphical-quote="false"] blockquote {
+ border-style: none;
+ padding: 0;
+}
+
+.moz-text-plain[graphical-quote="true"] .moz-txt-citetags {
+ display: none;
+}
+
+span.moz-txt-underscore {
+ text-decoration: underline;
+}
+
+span.moz-txt-formfeed {
+ display: block;
+ height: 100%;
+}
+
+/* ::::: attached images ::::: */
+.moz-attached-image[overflowing="true"] {
+ cursor: -moz-zoom-out;
+}
+
+.moz-attached-image[isshrunk="true"] {
+ cursor: -moz-zoom-in;
+ max-width: 100%;
+}
+
+
+/* ::::: vcard ::::: */
+
+.moz-vcard-table {
+ -moz-border-radius: 8px;
+ border: thin solid gray;
+ margin-top: 10px;
+}
+
+.moz-vcard-property {
+ font-size: 80%;
+ color: gray;
+}
+
+.moz-vcard-title-property {
+}
+
+.moz-vcard-badge {
+ height: 24px;
+ width: 24px;
+ background-color: transparent;
+ display: block;
+ background-image: url("chrome://messenger/skin/addressbook/icons/abcard-large.png");
+}
+
+.moz-vcard-badge:hover {
+ -moz-image-region: rect(30px 30px 60px 0px);
+}
+
+.moz-vcard-badge:focus {
+ outline: none;
+}
+
+/* New style feed summary body. */
+body[selected="false"] {
+ display: none;
+}
+
+/* Old style feeds. */
+#_mailrssiframe {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: none;
+}
--- a/mail/themes/gnomestripe/mail/messageHeader.css
+++ b/mail/themes/gnomestripe/mail/messageHeader.css
@@ -46,34 +46,26 @@
color: WindowText;
background-color: AppWorkspace;
border-bottom: 2px groove ThreeDLightShadow;
padding: 0.4ex;
}
/* ::::: msg header toolbars ::::: */
-#collapsedHeaderView {
- min-width: 1px;
-}
-
#expandedHeaderView {
overflow-y: auto;
overflow-x: hidden;
max-height: 14em;
}
#variousHeadersBox{
padding-bottom: 1em;
}
-#collapsedHeaderDisplayName {
- text-align: left;
-}
-
/* ::::: msg header buttons ::::: */
.headerContainer
{
min-width: 1px;
}
#otherActionsButton {
margin-bottom: .1em;
@@ -91,38 +83,16 @@
list-style-image: url("chrome://messenger/skin/icons/arrow-dn-grey.png");
}
.msgHeaderView-flat-button[type="menu"]:hover > .button-box > .button-menu-dropmarker > .dropmarker-icon,
.msgHeaderView-flat-button[type="menu-button"]:hover > .button-menubutton-dropmarker > .dropmarker-icon {
list-style-image: url("chrome://messenger/skin/icons/arrow-dn-black.png");
}
-#collapsedsubjectValue {
- -moz-margin-start: 3px !important;
- -moz-box-align: stretch;
- font-weight: bold;
-}
-
-#collapseddateValue {
- -moz-box-align: stretch;
- text-align: right;
- -moz-padding-end: 0.5em !important;
-}
-
-#showDetailsButton {
- -moz-appearance: none !important;
- width: 22px;
- padding-bottom: 0px !important;
- height: 22px;
- background-image: url("chrome://messenger/skin/icons/chevron.png");
- background-repeat: no-repeat;
- background-position: center center;
-}
-
/* ::::: expanded header pane ::::: */
header-view-button-box {
padding: 0px;
}
#expandedfromBox {
padding-top: 0.5em;
@@ -380,43 +350,16 @@ description[selectable="true"]:focus > d
.headerValueUrl:hover {
color: red;
}
.headerField {
color: inherit;
}
-#collapsedsubjectBox {
- margin: 0px;
- padding: 0px;
-}
-
-#collapsedfromBox {
- padding-top: 0.5em;
-}
-
-#collapsedfromValue > .headerNameBox {
- display: none;
-}
-
-#collapsedtoCcBccValue > .headerNameBox {
- display: none;
-}
-
-#collapsedtoCcBccValue > .headerValueBox[singleline] {
- padding-top: 0;
- padding-bottom: 0;
-}
-
-#collapsedtoCcBccValue > .headerValueBox:not([singleline]) {
- padding-top: 0.5em;
- padding-bottom: 0;
-}
-
.moreIndicator {
font-weight: bold;
}
.moreIndicator:hover {
text-decoration: underline;
color: darkred;
}
@@ -554,23 +497,19 @@ mail-emailaddress[selected="true"] .emai
.addresstwisty[open] {
list-style-image:url("chrome://messenger/skin/icons/arrow/arrow-down-dim.png");
}
.addresstwisty[open]:hover {
list-style-image:url("chrome://messenger/skin/icons/arrow/arrow-down.png");
}
-/* ::::: view expand and collapse twisties ::::: */
+/* ::::: view expand twisty ::::: */
.expandHeaderViewButton {
list-style-image: url("chrome://global/skin/tree/twisty-open.png");
}
-.collapsedHeaderViewButton {
- list-style-image: url("chrome://global/skin/tree/twisty-clsd.png");
-}
-
mail-multi-emailHeaderField,
mail-headerfield {
margin: 0;
padding: 0;
}
--- a/mail/themes/gnomestripe/mail/messageQuotes.css
+++ b/mail/themes/gnomestripe/mail/messageQuotes.css
@@ -1,8 +1,12 @@
+/* Because this sheet is loaded synchronously while the user is waiting for the
+ compose window to appear, it must not @import a ton of other things, and
+ especially must not trigger network access. */
+
/* ===== messageQuotes.css =================================================
== Shared styles such as block quote colors and signature style
== between the message body during
== message display and the mail editor instance for mail compose.
======================================================================= */
/* ::::: signature ::::: */
--- a/mail/themes/gnomestripe/mail/messenger.css
+++ b/mail/themes/gnomestripe/mail/messenger.css
@@ -14,16 +14,28 @@ description.error {
.toolbar-primary {
-moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar");
}
toolbar[printpreview="true"] {
-moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar");
}
+/*
+ * Override the menulist icon forbidding in menu.css so that we can show
+ * check-marks. bug 443516
+ */
+.menulist-menupopup > menuitem > .menu-iconic-left,
+menulist > menupopup > menuitem > .menu-iconic-left,
+.menulist-menupopup > menu > .menu-iconic-left,
+menulist > menupopup > menu > .menu-iconic-left {
+ display: -moz-box;
+}
+
+
/* ::::: throbber ::::: */
#navigator-throbber {
-moz-appearance: none;
-moz-user-focus: ignore;
margin: 0 !important;
border: none !important;
padding: 0px !important;
--- a/mail/themes/gnomestripe/mail/tabmail.css
+++ b/mail/themes/gnomestripe/mail/tabmail.css
@@ -70,16 +70,20 @@
padding-top: 1px;
-moz-padding-start: 1px;
}
.tabmail-tab[busy] > .tab-image-middle > .tab-icon > .tab-icon-image {
list-style-image: url("chrome://global/skin/icons/loading_16.png") !important;
}
+.tabmail-tab[thinking] > .tab-image-middle > .tab-icon > .tab-icon-image {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png") !important;
+}
+
.tabmail-tab[selected="true"] {
font-weight: bold;
}
.tabmail-tab[selected="true"] > .tab-image-middle > .tab-text {
opacity: 1.0 !important;
}
deleted file mode 100644
--- a/mail/themes/gnomestripe/mail/tree.css
+++ /dev/null
@@ -1,347 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Communicator client code, released
- * March 31, 1998.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998-2001
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Joe Hewitt (hewitt@netscape.com)
- * Dean Tessman (dean_tessman@hotmail.com)
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-/* ===== tree.css ===================================================
- == Styles used by the XUL outline element.
- ======================================================================= */
-
-@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
-
-/* ::::: tree ::::: */
-
-tree {
- margin: 0px 4px;
- border: 2px solid;
- -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
- -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
- -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
- -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
- background-color: -moz-Field;
- color: -moz-FieldText;
- -moz-appearance: listbox;
-}
-
-/* ::::: tree rows ::::: */
-
-treechildren::-moz-tree-row {
- border: 1px solid transparent;
- background-color: transparent;
- min-height: 18px;
- height: 1.3em;
-}
-
-treechildren::-moz-tree-row(selected) {
- background-color: -moz-Dialog;
-}
-
-treechildren::-moz-tree-row(selected, focus) {
- background-color: Highlight;
-}
-
-treechildren::-moz-tree-row(current, focus) {
- border: 1px dotted #000000;
-}
-
-treechildren::-moz-tree-row(selected, current, focus) {
- border: 1px dotted #C0C0C0;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-row,
-tree[seltype="text"] > treechildren::-moz-tree-row {
- border: none;
- background-color: transparent;
-}
-
-/* ::::: tree cells ::::: */
-
-treechildren::-moz-tree-cell {
- padding: 0px 2px;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell-text,
-tree[seltype="text"] > treechildren::-moz-tree-cell-text,
-treechildren::-moz-tree-cell-text {
- color: inherit;
-}
-
-treechildren::-moz-tree-cell-text(selected) {
- color: -moz-DialogText;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell {
- border: 1px solid transparent;
- padding: 0px 1px;
-}
-
-tree[seltype="text"] > treechildren::-moz-tree-cell-text {
- border: 1px solid transparent;
- padding: 0px 1px 1px;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell(active, selected) {
- background-color: -moz-Dialog;
-}
-tree[seltype="cell"] > treechildren::-moz-tree-cell-text(active, selected) {
- color: -moz-DialogText;
-}
-
-tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, selected) {
- background-color: -moz-Dialog;
- color: -moz-DialogText;
-}
-
-treechildren::-moz-tree-cell-text(selected, focus) {
- color: HighlightText;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell(active, selected, focus) {
- background-color: Highlight;
-}
-tree[seltype="cell"] > treechildren::-moz-tree-cell-text(active, selected, focus) {
- color: HighlightText;
-}
-
-tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, selected, focus) {
- background-color: Highlight;
- color: HighlightText;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell(active, current, focus) {
- border: 1px dotted #000000;
-}
-
-tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, current, focus) {
- border: 1px dotted #000000;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell(active, selected, current, focus) {
- border: 1px dotted #C0C0C0;
-}
-
-tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, selected, current, focus) {
- border: 1px dotted #C0C0C0;
-}
-
-
-/* ::::: lines connecting cells ::::: */
-
-tree[seltype="cell"] > treechildren::-moz-tree-line,
-tree[seltype="text"] > treechildren::-moz-tree-line,
-treechildren::-moz-tree-line {
- border: 1px dotted ThreeDShadow;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-line(active, selected, focus),
-treechildren::-moz-tree-line(selected, focus) {
- border: 1px dotted HighlightText;
-}
-
-
-/* ::::: tree separator ::::: */
-
-treechildren::-moz-tree-separator {
- border-top: 1px solid ThreeDShadow;
- border-bottom: 1px solid ThreeDHighlight;
-}
-
-
-/* ::::: drop feedback ::::: */
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell-text(primary, dropOn),
-tree[seltype="text"] > treechildren::-moz-tree-cell-text(primary, dropOn),
-treechildren::-moz-tree-cell-text(primary, dropOn) {
- background-color: Highlight;
- color: HighlightText;
-}
-
-treechildren::-moz-tree-drop-feedback {
- background-color: Highlight;
- width: 50px;
- height: 2px;
- -moz-margin-start: 5px;
-}
-
-/* ::::: tree progress meter ::::: */
-
-treechildren::-moz-tree-progressmeter {
- margin: 2px 4px;
- border: 2px solid;
- -moz-border-top-colors: ThreeDShadow -moz-Dialog;
- -moz-border-right-colors: ThreeDHighlight -moz-Dialog;
- -moz-border-bottom-colors: ThreeDHighlight -moz-Dialog;
- -moz-border-left-colors: ThreeDShadow -moz-Dialog;
- background-color: -moz-Dialog;
- color: ThreeDShadow;
-}
-
-treechildren::-moz-tree-progressmeter(progressUndetermined) {
- list-style-image: url("chrome://global/skin/progressmeter/progressmeter-busy.gif");
-}
-
-treechildren::-moz-tree-cell-text(progressmeter) {
- margin: 2px 4px;
-}
-
-/* ::::: tree columns ::::: */
-
-treecol,
-treecolpicker {
- -moz-appearance: treeheadercell;
- -moz-box-align: center;
- -moz-box-pack: center;
- border: 2px solid;
- -moz-border-top-colors: ThreeDHighlight ThreeDLightShadow;
- -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
- -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow;
- -moz-border-left-colors: ThreeDHighlight ThreeDLightShadow;
- background-color: -moz-Dialog;
- color: -moz-DialogText;
- padding-top: 1px;
- padding-bottom: 0px;
- -moz-padding-start: 5px;
- -moz-padding-end: 4px;
-}
-
-.treecol-image {
- padding-top: 1px;
- padding-bottom: 0px;
- -moz-padding-start: 2px;
- -moz-padding-end: 1px;
-}
-
-.treecol-text {
- margin: 0px !important;
-}
-
-treecol[hideheader="true"] {
- -moz-appearance: none;
- border: none;
- padding: 0;
-}
-
-/* ..... internal box ..... */
-
-treecol:hover:active,
-treecolpicker:hover:active {
- border-top: 2px solid;
- border-right: 1px solid;
- border-bottom: 1px solid;
- border-left: 2px solid;
- -moz-border-top-colors: ThreeDShadow -moz-Dialog;
- -moz-border-right-colors: ThreeDShadow;
- -moz-border-bottom-colors: ThreeDShadow;
- -moz-border-left-colors: ThreeDShadow -moz-Dialog;
-}
-
-
-/* ::::: column drag and drop styles ::::: */
-
-treecol[dragging="true"] {
- -moz-border-top-colors: ThreeDDarkShadow transparent !important;
- -moz-border-right-colors: ThreeDDarkShadow transparent!important;
- -moz-border-bottom-colors: ThreeDDarkShadow transparent !important;
- -moz-border-left-colors: ThreeDDarkShadow transparent !important;
- background-color: ThreeDShadow !important;
- color: ThreeDHighlight !important;
-}
-
-treecol[insertafter="true"] {
- -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
-}
-
-treecol[insertbefore="true"] {
- -moz-border-left-colors: ThreeDDarkShadow ThreeDShadow;
-}
-
-treechildren::-moz-tree-column(insertbefore) {
- border-left: 1px solid ThreeDShadow;
-}
-
-treechildren::-moz-tree-column(insertafter) {
- border-right: 1px solid ThreeDShadow;
-}
-
-/* ::::: sort direction indicator ::::: */
-
-.treecol-sortdirection {
- list-style-image: none;
- width: 8px; /* The image's width is 7 pixels */
-}
-
-.treecol-sortdirection[sortDirection="ascending"] {
- list-style-image: url("chrome://global/skin/tree/sort-asc.gif");
-}
-
-.treecol-sortdirection[sortDirection="descending"] {
- list-style-image: url("chrome://global/skin/tree/sort-dsc.gif");
-}
-
-/* ::::: column picker ::::: */
-
-.tree-columnpicker-icon {
- list-style-image: url("chrome://global/skin/tree/columnpicker.gif");
-}
-
-/* ::::: twisty ::::: */
-
-treechildren::-moz-tree-twisty {
- -moz-padding-end: 2px;
- width: 10px; /* The image's width is 9 pixels */
- list-style-image: url("chrome://global/skin/tree/twisty-clsd.png");
-}
-
-treechildren::-moz-tree-twisty(open) {
- width: 10px; /* The image's width is 9 pixels */
- list-style-image: url("chrome://global/skin/tree/twisty-open.png");
-}
-
-treechildren::-moz-tree-indentation {
- width: 16px;
-}
-
-/* ::::: gridline style ::::: */
-
-treechildren.gridlines::-moz-tree-cell {
- border-right: 1px solid GrayText;
- border-bottom: 1px solid GrayText;
-}
-
-treechildren.gridlines::-moz-tree-row {
- border: none;
-}
--- a/mail/themes/pinstripe/jar.mn
+++ b/mail/themes/pinstripe/jar.mn
@@ -108,19 +108,17 @@ classic.jar:
skin/classic/messenger/smime/icons/sbSignNotOk.gif (mail/smime/sbSignNotOk.gif)
skin/classic/messenger/smime/icons/sbCryptoOk.gif (mail/smime/sbCryptoOk.gif)
skin/classic/messenger/smime/icons/sbCryptoNotOk.gif (mail/smime/sbCryptoNotOk.gif)
skin/classic/messenger/smime/icons/hdrSignOk.gif (mail/smime/hdrSignOk.gif)
skin/classic/messenger/smime/icons/hdrSignUnknown.gif (mail/smime/hdrSignUnknown.gif)
skin/classic/messenger/smime/icons/hdrSignNotOk.gif (mail/smime/hdrSignNotOk.gif)
skin/classic/messenger/smime/icons/hdrCryptoOk.gif (mail/smime/hdrCryptoOk.gif)
skin/classic/messenger/smime/icons/hdrCryptoNotOk.gif (mail/smime/hdrCryptoNotOk.gif)
- skin/classic/messenger/icons/chevron.png (mail/icons/chevron.png)
skin/classic/messenger/icons/twisty-open.gif (mail/icons/twisty-open.gif)
- skin/classic/messenger/icons/twisty-closed.gif (mail/icons/twisty-closed.gif)
skin/classic/messenger/icons/spin-buttons-active.png (mail/icons/spin-buttons-active.png)
skin/classic/messenger/icons/spin-buttons.png (mail/icons/spin-buttons.png)
skin/classic/messenger/icons/sidebar-item.png (mail/icons/sidebar-item.png)
skin/classic/messenger/icons/attachment-deleted.png (mail/icons/attachment-deleted.png)
skin/classic/messenger/icons/attachment-deleted-large.png (mail/icons/attachment-deleted-large.png)
skin/classic/messenger/icons/attachment-col.png (mail/icons/attachment-col.png)
skin/classic/messenger/icons/attachment-selected.png (mail/icons/attachment-selected.png)
skin/classic/messenger/icons/attachment.png (mail/icons/attachment.png)
deleted file mode 100644
index 246d5ee8bd00d75dfe1aecb18a70a4c898b4f2b7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index d87a58b82a6c2b30d6193dc1bbebb797a4234e57..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/mail/themes/pinstripe/mail/messageBody.css
+++ b/mail/themes/pinstripe/mail/messageBody.css
@@ -176,22 +176,22 @@ blockquote[type=cite] > blockquote {
border-color: green !important;
}
blockquote[type=cite] > blockquote > blockquote {
color: maroon !important;
border-color: maroon !important;
}
-/* Style new format rss summary vs web page */
-body[selected="false"],
-iframe[selected="false"] {
+/* New style feed summary body. */
+body[selected="false"] {
display: none;
}
-
+
+/* Old style feeds. */
#_mailrssiframe {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
--- a/mail/themes/pinstripe/mail/messageHeader.css
+++ b/mail/themes/pinstripe/mail/messageHeader.css
@@ -45,34 +45,26 @@
.main-header-area {
color: rgb(46, 52, 54); /* Aluminium 6 */
background-color: rgb(238, 238, 236); /* Aluminium 1 */
border-bottom: 2px groove rgb(186,189,182); /* Aluminium 3 */
padding: 0.6ex;
}
/* ::::: msg header toolbars ::::: */
-#collapsedHeaderView {
- min-width: 1px;
-}
-
#expandedHeaderView {
overflow-x: hidden;
overflow-y: visible;
max-height: 14em;
}
#variousHeadersBox{
padding-bottom: 1em;
}
-#collapsedHeaderDisplayName {
- text-align: left;
-}
-
/* ::::: msg header buttons ::::: */
.headerContainer
{
min-width: 1px;
}
#otherActionsButton {
margin-bottom: 0.1em;
@@ -101,59 +93,16 @@
list-style-image: url("chrome://messenger/skin/icons/arrow-dn-grey.png");
}
.msgHeaderView-flat-button[type="menu"]:hover > .button-box > .button-menu-dropmarker > .dropmarker-icon,
.msgHeaderView-flat-button[type="menu-button"]:hover > .button-menubutton-dropmarker > .dropmarker-icon {
list-style-image: url("chrome://messenger/skin/icons/arrow-dn-black.png");
}
-#hideDetailsButton {
- font-weight: normal;
- font-size: 100%;
- background: none;
- color: #41413F; /* higher contrast */
- border: 2px solid transparent;
- font-size: 100%;
-}
-
-#hideDetailsButton:hover {
- color: black;
- background-color: rgb(230,231,227);
- border: 2px solid #C0C3C6;
-}
-
-#collapsedsubjectValue {
- -moz-margin-start: 3px !important;
- -moz-box-align: stretch;
- font-weight: bold;
-}
-
-#collapseddateValue {
- -moz-box-align: stretch;
- text-align: right;
- -moz-padding-end: 0.5em !important;
-}
-
-#showDetailsButton {
- -moz-appearance: none !important;
- border: 2px solid transparent;
- background-color: transparent;
- width: 22px;
- padding-bottom: 0px !important;
- background-image: url("chrome://messenger/skin/icons/chevron.png");
- background-repeat: no-repeat;
- background-position: center center;
-}
-
-#showDetailsButton:hover {
- background-color: rgb(230,231,227);
- border: 2px solid #C0C3C6;
-}
-
.hdrTrashButton {
-moz-box-orient: vertical;
list-style-image: url("chrome://messenger/skin/icons/folder-trash.png");
}
/* ::::: expanded header pane ::::: */
header-view-button-box {
@@ -437,39 +386,16 @@ description[selectable="true"]:focus > d
.headerValueUrl:hover {
color: red;
}
.headerField {
color: inherit;
}
-#collapsedsubjectBox {
- margin: 0px;
- padding: 0px;
-}
-
-#collapsedfromValue > .headerNameBox {
- display: none;
-}
-
-#collapsedtoCcBccValue > .headerNameBox {
- display: none;
-}
-
-#collapsedtoCcBccValue > .headerValueBox[singleline] {
- padding-top: 0;
- padding-bottom: 0;
-}
-
-#collapsedtoCcBccValue > .headerValueBox:not([singleline]) {
- padding-top: 0.5em;
- padding-bottom: 0;
-}
-
.moreIndicator {
font-weight: bold;
font-size: small;
}
.moreIndicator:hover {
text-decoration: underline;
color: darkred;
@@ -619,19 +545,15 @@ mail-emailaddress[selected="true"] .emai
}
/* ::::: view expand and collapse twisties ::::: */
.expandHeaderViewButton {
list-style-image:url("chrome://messenger/skin/icons/twisty-open.gif");
}
-.collapsedHeaderViewButton {
- list-style-image:url("chrome://messenger/skin/icons/twisty-closed.gif");
-}
-
/* ::::: collapsed view styles ::::: */
mail-multi-emailHeaderField,
mail-headerfield {
margin: 0;
padding: 0;
}
--- a/mail/themes/pinstripe/mail/tabmail.css
+++ b/mail/themes/pinstripe/mail/tabmail.css
@@ -71,16 +71,20 @@
.tabmail-tab[selected="true"] > .tab-icon {
list-style-image: url("chrome://global/skin/tree/item.png");
}
.tabmail-tab[busy] > .tab-icon {
list-style-image: url("chrome://global/skin/icons/loading_16.png") !important;
}
+.tabmail-tab[thinking] > .tab-icon {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png") !important;
+}
+
.tabmail-tab:hover > .tab-icon,
.tabmail-tab[selected="true"] > .tab-icon {
opacity: 1;
}
.tab-text {
margin-top: 3px !important;
margin-bottom: 0 !important;
--- a/mail/themes/qute/jar.mn
+++ b/mail/themes/qute/jar.mn
@@ -1,12 +1,9 @@
classic.jar:
-+ skin/classic/global/tree.css (mail/tree.css)
-+ skin/classic/global/tree/sort-asc.gif (mail/icons/sort-asc.gif)
-+ skin/classic/global/tree/sort-dsc.gif (mail/icons/sort-dsc.gif)
icon.png (mail/icon.png)
preview.png (mail/preview.png)
% skin communicator classic/1.0 %skin/classic/communicator/
skin/classic/communicator/communicator.css (mail/communicator.css)
skin/classic/communicator/smileys.css (mail/smileys.css)
skin/classic/communicator/icons/smileys/smiley-smile.png (mail/icons/smiley-smile.png)
skin/classic/communicator/icons/smileys/smiley-frown.png (mail/icons/smiley-frown.png)
skin/classic/communicator/icons/smileys/smiley-wink.png (mail/icons/smiley-wink.png)
@@ -206,18 +203,16 @@ classic.jar:
skin/classic/messenger/icons/arrow/arrow-left.png (mail/icons/arrow/arrow-left.png)
skin/classic/messenger/icons/arrow/arrow-right.png (mail/icons/arrow/arrow-right.png)
skin/classic/messenger/icons/arrow/arrow-up.png (mail/icons/arrow/arrow-up.png)
skin/classic/messenger/icons/arrow/arrow-down.png (mail/icons/arrow/arrow-down.png)
skin/classic/messenger/icons/arrow/arrow-left-dim.png (mail/icons/arrow/arrow-left-dim.png)
skin/classic/messenger/icons/arrow/arrow-right-dim.png (mail/icons/arrow/arrow-right-dim.png)
skin/classic/messenger/icons/arrow/arrow-up-dim.png (mail/icons/arrow/arrow-up-dim.png)
skin/classic/messenger/icons/arrow/arrow-down-dim.png (mail/icons/arrow/arrow-down-dim.png)
- skin/classic/messenger/icons/chevron.png (mail/icons/chevron.png)
- skin/classic/messenger/icons/croppedchevron.png (mail/icons/croppedchevron.png)
skin/classic/messenger/tagbg.png (mail/tagbg.png)
% skin messenger-newsblog classic/1.0 %skin/classic/messenger-newsblog/ os=WINNT osversion<6
% skin messenger-newsblog classic/1.0 %skin/classic/messenger-newsblog/ os!=WINNT
skin/classic/messenger-newsblog/feed-subscriptions.css (mail/newsblog/feed-subscriptions.css)
skin/classic/messenger-newsblog/icons/rss-feed.png (mail/newsblog/rss-feed.png)
skin/classic/messenger-newsblog/icons/server-rss.png (mail/newsblog/server-rss.png)
#ifdef XP_WIN
% skin messenger classic/1.0 %skin/classic/aero/messenger/ os=WINNT osversion>=6
@@ -393,17 +388,15 @@ classic.jar:
skin/classic/aero/messenger/icons/arrow/arrow-left.png (mail/icons/arrow/arrow-left.png)
skin/classic/aero/messenger/icons/arrow/arrow-right.png (mail/icons/arrow/arrow-right.png)
skin/classic/aero/messenger/icons/arrow/arrow-up.png (mail/icons/arrow/arrow-up.png)
skin/classic/aero/messenger/icons/arrow/arrow-down.png (mail/icons/arrow/arrow-down.png)
skin/classic/aero/messenger/icons/arrow/arrow-left-dim.png (mail/icons/arrow/arrow-left-dim.png)
skin/classic/aero/messenger/icons/arrow/arrow-right-dim.png (mail/icons/arrow/arrow-right-dim.png)
skin/classic/aero/messenger/icons/arrow/arrow-up-dim.png (mail/icons/arrow/arrow-up-dim.png)
skin/classic/aero/messenger/icons/arrow/arrow-down-dim.png (mail/icons/arrow/arrow-down-dim.png)
- skin/classic/aero/messenger/icons/chevron.png (mail/icons/chevron.png)
- skin/classic/aero/messenger/icons/croppedchevron.png (mail/icons/croppedchevron.png)
skin/classic/aero/messenger/tagbg.png (mail/tagbg.png)
% skin messenger-newsblog classic/1.0 %skin/classic/aero/messenger-newsblog/ os=WINNT osversion>=6
skin/classic/aero/messenger-newsblog/feed-subscriptions.css (mail/newsblog/feed-subscriptions.css)
skin/classic/aero/messenger-newsblog/icons/rss-feed.png (mail/newsblog/rss-feed.png)
skin/classic/aero/messenger-newsblog/icons/server-rss.png (mail/newsblog/server-rss.png)
#endif
deleted file mode 100644
index 246d5ee8bd00d75dfe1aecb18a70a4c898b4f2b7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index e63e4ecef93b11108763929b2e50d64fae0fdca0..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100755
index b341abde70d4854bce80a79007ebde65e6b21c04..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100755
index bfeb6a8a8ae42839b54de9a4645e56826a1a1bf7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/mail/themes/qute/mail/messageBody.css
+++ b/mail/themes/qute/mail/messageBody.css
@@ -145,22 +145,22 @@ span.moz-txt-formfeed {
.moz-vcard-badge:hover {
-moz-image-region: rect(30px 30px 60px 0px);
}
.moz-vcard-badge:focus {
outline: none;
}
-/* Style new format rss summary vs web page */
-body[selected="false"],
-iframe[selected="false"] {
+/* New style feed summary body. */
+body[selected="false"] {
display: none;
}
-
+
+/* Old style feeds. */
#_mailrssiframe {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
--- a/mail/themes/qute/mail/messageHeader.css
+++ b/mail/themes/qute/mail/messageHeader.css
@@ -46,74 +46,37 @@
color: WindowText;
background-color: ButtonFace;
border-bottom: 2px groove ThreeDLightShadow;
padding: 0.4ex;
}
/* ::::: msg header toolbars ::::: */
-#collapsedHeaderView {
- min-width: 1px;
-}
-
#expandedHeaderView {
overflow-y: auto;
overflow-x: hidden;
max-height: 14em;
}
#variousHeadersBox{
padding-bottom: 1em;
}
-#collapsedHeaderDisplayName {
- text-align: left;
-}
-
/* ::::: msg header buttons ::::: */
.headerContainer
{
min-width: 1px;
}
#otherActionsButton {
margin-bottom: .1em;
padding-top: 0px;
}
-#collapsedsubjectValue {
- -moz-margin-start: 3px !important;
- -moz-box-align: stretch;
- font-weight: bold;
-}
-
-#collapseddateValue {
- -moz-box-align: stretch;
- text-align: right;
- -moz-padding-end: 0.5em !important;
-}
-
-#showDetailsButton {
- width: 26px;
- padding-bottom: 0px;
- height: 23px;
- border: solid 3px transparent;
- margin: 0;
- text-align: center;
- list-style-image: url("chrome://messenger/skin/icons/croppedchevron.png");
-}
-
-#showDetailsButton > .button-box,
-#showDetailsButton > .button-box > .button-icon,
-#showDetailsButton > .button-box > .button-text{
- margin: 0;
- padding: 0;
-}
-
/* ::::: expanded header pane ::::: */
header-view-button-box {
padding: 0px;
}
#expandedfromBox {
padding-top: 0.5em;
@@ -357,43 +320,16 @@ description[selectable="true"]:focus > d
.headerValueUrl:hover {
color: red;
}
.headerField {
color: inherit;
}
-#collapsedsubjectBox {
- margin: 0px;
- padding: 0px;
-}
-
-#collapsedfromBox {
- padding-top: 0.5em;
-}
-
-#collapsedfromValue > .headerNameBox {
- display: none;
-}
-
-#collapsedtoCcBccValue > .headerNameBox {
- display: none;
-}
-
-#collapsedtoCcBccValue > .headerValueBox[singleline] {
- padding-top: 0;
- padding-bottom: 0;
-}
-
-#collapsedtoCcBccValue > .headerValueBox:not([singleline]) {
- padding-top: 0.5em;
- padding-bottom: 0;
-}
-
.moreIndicator {
font-weight: bold;
}
.moreIndicator:hover {
text-decoration: underline;
color: darkred;
}
@@ -537,17 +473,13 @@ mail-emailaddress[selected="true"] .emai
}
/* ::::: view expand and collapse twisties ::::: */
.expandHeaderViewButton {
list-style-image: url("chrome://global/skin/tree/twisty-open.png");
}
-.collapsedHeaderViewButton {
- list-style-image: url("chrome://global/skin/tree/twisty-clsd.png");
-}
-
mail-multi-emailHeaderField,
mail-headerfield {
margin: 0;
padding: 0;
}
--- a/mail/themes/qute/mail/messageQuotes.css
+++ b/mail/themes/qute/mail/messageQuotes.css
@@ -1,8 +1,12 @@
+/* Because this sheet is loaded synchronously while the user is waiting for the
+ compose window to appear, it must not @import a ton of other things, and
+ especially must not trigger network access. */
+
/* ===== messageQuotes.css =================================================
== Shared styles such as block quote colors and signature style
== between the message body during
== message display and the mail editor instance for mail compose.
======================================================================= */
/* ::::: signature ::::: */
--- a/mail/themes/qute/mail/tabmail.css
+++ b/mail/themes/qute/mail/tabmail.css
@@ -70,16 +70,20 @@
padding-top: 1px;
-moz-padding-start: 1px;
}
.tabmail-tab[busy] > .tab-image-middle > .tab-icon > .tab-icon-image {
list-style-image: url("chrome://global/skin/icons/loading_16.png") !important;
}
+.tabmail-tab[thinking] > .tab-image-middle > .tab-icon > .tab-icon-image {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png") !important;
+}
+
.tabmail-tab[selected="true"] {
font-weight: bold;
}
.tabmail-tab[selected="true"] > .tab-image-middle > .tab-text {
opacity: 1.0 !important;
}
deleted file mode 100644
--- a/mail/themes/qute/mail/tree.css
+++ /dev/null
@@ -1,347 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Communicator client code, released
- * March 31, 1998.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998-2001
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Joe Hewitt (hewitt@netscape.com)
- * Dean Tessman (dean_tessman@hotmail.com)
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-/* ===== tree.css ===================================================
- == Styles used by the XUL outline element.
- ======================================================================= */
-
-@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
-
-/* ::::: tree ::::: */
-
-tree {
- margin: 0px 4px;
- border: 2px solid;
- -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
- -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
- -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
- -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
- background-color: -moz-Field;
- color: -moz-FieldText;
- -moz-appearance: listbox;
-}
-
-/* ::::: tree rows ::::: */
-
-treechildren::-moz-tree-row {
- border: 1px solid transparent;
- background-color: transparent;
- min-height: 18px;
- height: 1.3em;
-}
-
-treechildren::-moz-tree-row(selected) {
- background-color: -moz-Dialog;
-}
-
-treechildren::-moz-tree-row(selected, focus) {
- background-color: Highlight;
-}
-
-treechildren::-moz-tree-row(current, focus) {
- border: 1px dotted #000000;
-}
-
-treechildren::-moz-tree-row(selected, current, focus) {
- border: 1px dotted #C0C0C0;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-row,
-tree[seltype="text"] > treechildren::-moz-tree-row {
- border: none;
- background-color: transparent;
-}
-
-/* ::::: tree cells ::::: */
-
-treechildren::-moz-tree-cell {
- padding: 0px 2px;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell-text,
-tree[seltype="text"] > treechildren::-moz-tree-cell-text,
-treechildren::-moz-tree-cell-text {
- color: inherit;
-}
-
-treechildren::-moz-tree-cell-text(selected) {
- color: -moz-DialogText;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell {
- border: 1px solid transparent;
- padding: 0px 1px;
-}
-
-tree[seltype="text"] > treechildren::-moz-tree-cell-text {
- border: 1px solid transparent;
- padding: 0px 1px 1px;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell(active, selected) {
- background-color: -moz-Dialog;
-}
-tree[seltype="cell"] > treechildren::-moz-tree-cell-text(active, selected) {
- color: -moz-DialogText;
-}
-
-tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, selected) {
- background-color: -moz-Dialog;
- color: -moz-DialogText;
-}
-
-treechildren::-moz-tree-cell-text(selected, focus) {
- color: HighlightText;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell(active, selected, focus) {
- background-color: Highlight;
-}
-tree[seltype="cell"] > treechildren::-moz-tree-cell-text(active, selected, focus) {
- color: HighlightText;
-}
-
-tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, selected, focus) {
- background-color: Highlight;
- color: HighlightText;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell(active, current, focus) {
- border: 1px dotted #000000;
-}
-
-tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, current, focus) {
- border: 1px dotted #000000;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell(active, selected, current, focus) {
- border: 1px dotted #C0C0C0;
-}
-
-tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, selected, current, focus) {
- border: 1px dotted #C0C0C0;
-}
-
-
-/* ::::: lines connecting cells ::::: */
-
-tree[seltype="cell"] > treechildren::-moz-tree-line,
-tree[seltype="text"] > treechildren::-moz-tree-line,
-treechildren::-moz-tree-line {
- border: 1px dotted ThreeDShadow;
-}
-
-tree[seltype="cell"] > treechildren::-moz-tree-line(active, selected, focus),
-treechildren::-moz-tree-line(selected, focus) {
- border: 1px dotted HighlightText;
-}
-
-
-/* ::::: tree separator ::::: */
-
-treechildren::-moz-tree-separator {
- border-top: 1px solid ThreeDShadow;
- border-bottom: 1px solid ThreeDHighlight;
-}
-
-
-/* ::::: drop feedback ::::: */
-
-tree[seltype="cell"] > treechildren::-moz-tree-cell-text(primary, dropOn),
-tree[seltype="text"] > treechildren::-moz-tree-cell-text(primary, dropOn),
-treechildren::-moz-tree-cell-text(primary, dropOn) {
- background-color: Highlight;
- color: HighlightText;
-}
-
-treechildren::-moz-tree-drop-feedback {
- background-color: Highlight;
- width: 50px;
- height: 2px;
- -moz-margin-start: 5px;
-}
-
-/* ::::: tree progress meter ::::: */
-
-treechildren::-moz-tree-progressmeter {
- margin: 2px 4px;
- border: 2px solid;
- -moz-border-top-colors: ThreeDShadow -moz-Dialog;
- -moz-border-right-colors: ThreeDHighlight -moz-Dialog;
- -moz-border-bottom-colors: ThreeDHighlight -moz-Dialog;
- -moz-border-left-colors: ThreeDShadow -moz-Dialog;
- background-color: -moz-Dialog;
- color: ThreeDShadow;
-}
-
-treechildren::-moz-tree-progressmeter(progressUndetermined) {
- list-style-image: url("chrome://global/skin/progressmeter/progressmeter-busy.gif");
-}
-
-treechildren::-moz-tree-cell-text(progressmeter) {
- margin: 2px 4px;
-}
-
-/* ::::: tree columns ::::: */
-
-treecol,
-treecolpicker {
- -moz-appearance: treeheadercell;
- -moz-box-align: center;
- -moz-box-pack: center;
- border: 2px solid;
- -moz-border-top-colors: ThreeDHighlight ThreeDLightShadow;
- -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
- -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow;
- -moz-border-left-colors: ThreeDHighlight ThreeDLightShadow;
- background-color: -moz-Dialog;
- color: -moz-DialogText;
- padding-top: 1px;
- padding-bottom: 0px;
- -moz-padding-start: 5px;
- -moz-padding-end: 4px;
-}
-
-.treecol-image {
- padding-top: 1px;
- padding-bottom: 0px;
- -moz-padding-start: 2px;
- -moz-padding-end: 1px;
-}
-
-.treecol-text {
- margin: 0px !important;
-}
-
-treecol[hideheader="true"] {
- -moz-appearance: none;
- border: none;
- padding: 0;
-}
-
-/* ..... internal box ..... */
-
-treecol:hover:active,
-treecolpicker:hover:active {
- border-top: 2px solid;
- border-right: 1px solid;
- border-bottom: 1px solid;
- border-left: 2px solid;
- -moz-border-top-colors: ThreeDShadow -moz-Dialog;
- -moz-border-right-colors: ThreeDShadow;
- -moz-border-bottom-colors: ThreeDShadow;
- -moz-border-left-colors: ThreeDShadow -moz-Dialog;
-}
-
-
-/* ::::: column drag and drop styles ::::: */
-
-treecol[dragging="true"] {
- -moz-border-top-colors: ThreeDDarkShadow transparent !important;
- -moz-border-right-colors: ThreeDDarkShadow transparent!important;
- -moz-border-bottom-colors: ThreeDDarkShadow transparent !important;
- -moz-border-left-colors: ThreeDDarkShadow transparent !important;
- background-color: ThreeDShadow !important;
- color: ThreeDHighlight !important;
-}
-
-treecol[insertafter="true"] {
- -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
-}
-
-treecol[insertbefore="true"] {
- -moz-border-left-colors: ThreeDDarkShadow ThreeDShadow;
-}
-
-treechildren::-moz-tree-column(insertbefore) {
- border-left: 1px solid ThreeDShadow;
-}
-
-treechildren::-moz-tree-column(insertafter) {
- border-right: 1px solid ThreeDShadow;
-}
-
-/* ::::: sort direction indicator ::::: */
-
-.treecol-sortdirection {
- list-style-image: none;
- width: 8px; /* The image's width is 7 pixels */
-}
-
-.treecol-sortdirection[sortDirection="ascending"] {
- list-style-image: url("chrome://global/skin/tree/sort-asc.gif");
-}
-
-.treecol-sortdirection[sortDirection="descending"] {
- list-style-image: url("chrome://global/skin/tree/sort-dsc.gif");
-}
-
-/* ::::: column picker ::::: */
-
-.tree-columnpicker-icon {
- list-style-image: url("chrome://global/skin/tree/columnpicker.gif");
-}
-
-/* ::::: twisty ::::: */
-
-treechildren::-moz-tree-twisty {
- -moz-padding-end: 2px;
- width: 10px; /* The image's width is 9 pixels */
- list-style-image: url("chrome://global/skin/tree/twisty-clsd.png");
-}
-
-treechildren::-moz-tree-twisty(open) {
- width: 10px; /* The image's width is 9 pixels */
- list-style-image: url("chrome://global/skin/tree/twisty-open.png");
-}
-
-treechildren::-moz-tree-indentation {
- width: 16px;
-}
-
-/* ::::: gridline style ::::: */
-
-treechildren.gridlines::-moz-tree-cell {
- border-right: 1px solid GrayText;
- border-bottom: 1px solid GrayText;
-}
-
-treechildren.gridlines::-moz-tree-row {
- border: none;
-}
--- a/mailnews/addrbook/src/nsAbView.cpp
+++ b/mailnews/addrbook/src/nsAbView.cpp
@@ -381,22 +381,35 @@ NS_IMETHODIMP nsAbView::IsSeparator(PRIn
return NS_OK;
}
NS_IMETHODIMP nsAbView::IsSorted(PRBool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
+#ifdef MOZILLA_1_9_1_BRANCH
NS_IMETHODIMP nsAbView::CanDrop(PRInt32 index, PRInt32 orientation, PRBool *_retval)
+#else
+NS_IMETHODIMP nsAbView::CanDrop(PRInt32 index,
+ PRInt32 orientation,
+ nsIDOMDataTransfer *dataTransfer,
+ PRBool *_retval)
+#endif
{
return NS_ERROR_NOT_IMPLEMENTED;
}
+#ifdef MOZILLA_1_9_1_BRANCH
NS_IMETHODIMP nsAbView::Drop(PRInt32 row, PRInt32 orientation)
+#else
+NS_IMETHODIMP nsAbView::Drop(PRInt32 row,
+ PRInt32 orientation,
+ nsIDOMDataTransfer *dataTransfer)
+#endif
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP nsAbView::GetParentIndex(PRInt32 rowIndex, PRInt32 *_retval)
{
*_retval = -1;
return NS_OK;
new file mode 100644
--- /dev/null
+++ b/mailnews/addrbook/test/unit/tail_addrbook.js
@@ -0,0 +1,1 @@
+load("../../mailnews/resources/mailShutdown.js");
--- a/mailnews/base/prefs/resources/content/AccountManager.js
+++ b/mailnews/base/prefs/resources/content/AccountManager.js
@@ -326,17 +326,17 @@ function checkUserServerChanges(showAler
// If username is changed remind users to change Your Name and Email Address.
// If serve name is changed and has defined filters then remind users to edit rules.
if (showAlert) {
var account = currentAccount
var filterList;
if (account && (newType != "nntp")) {
var server = account.incomingServer;
- filterList = server.getFilterList(null);
+ filterList = server.getEditableFilterList(null);
}
var userChangeText, serverChangeText;
var bundle = document.getElementById("bundle_prefs");
if ( (oldHost != newHost) && (filterList != undefined) && filterList.filterCount )
serverChangeText = bundle.getString("serverNameChanged");
if (oldUser != newUser)
userChangeText = bundle.getString("userNameChanged");
--- a/mailnews/base/prefs/resources/content/accountcreation/emailWizard.xul
+++ b/mailnews/base/prefs/resources/content/accountcreation/emailWizard.xul
@@ -180,17 +180,17 @@
</rows>
</grid>
<vbox>
<groupbox id="settingsbox" class="settings" hidden="true">
<vbox>
<vbox id="configarea">
<hbox>
<hbox flex="1">
- <description id="config_status_title"/>
+ <description id="config_status_title" flex="1"/>
</hbox>
<hbox pack="end">
<button id="stop_button"
oncommand="gEmailConfigWizard.onStop();"
label="&stop.label;" class="smaller-button"/>
<button id="edit_button"
oncommand="gEmailConfigWizard.onEdit();"
label="&edit.label;" class="smaller-button"
--- a/mailnews/base/prefs/resources/content/am-offline.js
+++ b/mailnews/base/prefs/resources/content/am-offline.js
@@ -128,18 +128,16 @@ function onPreInit(account, accountValue
if (gServerType == "pop3")
{
var pop3Server = gIncomingServer.QueryInterface(Components.interfaces.nsIPop3IncomingServer);
// hide retention settings for deferred accounts
if (pop3Server.deferredToAccount.length)
{
var retentionRadio = document.getElementById("retention.keepMsg");
retentionRadio.setAttribute("hidden", "true");
- var retentionCheckbox = document.getElementById("retention.keepUnread");
- retentionCheckbox.setAttribute("hidden", "true");
var retentionLabel = document.getElementById("retentionDescriptionPop");
retentionLabel.setAttribute("hidden", "true");
var applyToFlaggedCheckbox = document.getElementById("retention.applyToFlagged");
applyToFlaggedCheckbox.setAttribute("hidden", "true");
}
}
}
--- a/mailnews/base/prefs/resources/content/am-offline.xul
+++ b/mailnews/base/prefs/resources/content/am-offline.xul
@@ -116,18 +116,18 @@
<label id="retentionDescription" hidefor="imap,pop3" class="desc" control="retention.keepMsg">&retentionCleanup.label;</label>
<label id="retentionDescriptionImap" hidefor="movemail,pop3,nntp,none,rss" class="desc" control="retention.keepMsg">&retentionCleanupImap.label;</label>
<label id="retentionDescriptionPop" hidefor="movemail,imap,nntp,none,rss" class="desc" control="retention.keepMsg">&retentionCleanupPop.label;</label>
<radiogroup wsm_persist="true" hidefor="" id="retention.keepMsg" class="indent">
<radio wsm_persist="true" id="retention.keepAllMsg" value="1" accesskey="&retentionKeepAll.accesskey;"
label="&retentionKeepAll.label;" oncommand="onCheckKeepMsg();"/>
<hbox flex="1" align="center">
- <radio wsm_persist="true" id="retention.keepNewMsg" accesskey="&retentionKeepNew.accesskey;"
- value="3" label="&retentionKeepNew.label;" oncommand="onCheckKeepMsg();"/>
+ <radio wsm_persist="true" id="retention.keepNewMsg" accesskey="&retentionKeepRecent.accesskey;"
+ value="3" label="&retentionKeepRecent.label;" oncommand="onCheckKeepMsg();"/>
<textbox wsm_persist="true" id="retention.keepNewMsgMin"
type="number" min="1" increment="10" size="4" value="2000"
aria-labelledby="retention.keepNewMsg retention.keepNewMsgMin newMsgLabel"/>
<label value="&message.label;" control="retention.keepNewMsgMin" id="newMsgLabel"/>
</hbox>
<hbox flex="1" align="center">
<radio wsm_persist="true" id="retention.keepOldMsg" accesskey="&retentionKeepMsg.accesskey;"
value="2" label="&retentionKeepMsg.label;" oncommand="onCheckKeepMsg();"/>
@@ -136,28 +136,28 @@
aria-labelledby="retention.keepOldMsg retention.keepOldMsgMin oldMsgLabel"/>
<label value="&daysOld.label;" control="retention.keepOldMsgMin" id="oldMsgLabel"/>
</hbox>
</radiogroup>
<hbox align="center" class="indent">
<vbox>
<checkbox id="retention.keepUnread" wsm_persist="true"
- label="&retentionKeepUnread.label;"
- accesskey="&retentionKeepUnread.accesskey;"
+ label="&retentionKeepUnreadHidden.label;"
+ class="keepUnreadOnly" hidden="true"
checked="false"/>
<checkbox id="retention.applyToFlagged" wsm_persist="true"
- label="&retentionApplyToFlagged.label;"
+ label="&retentionApplyToFlagged.label;" hidefor=""
accesskey="&retentionApplyToFlagged.accesskey;"
checked="true"/>
</vbox>
</hbox>
<hbox align="center" class="indent" hidefor="movemail,pop3,imap,none,rss">
- <checkbox wsm_persist="true" id="nntp.removeBody" accesskey="&nntpRemoveBody.accesskey;"
- label="&nntpRemoveBody.label;" oncommand="onCheckItem('nntp.removeBodyMin','nntp.removeBody');"/>
+ <checkbox wsm_persist="true" id="nntp.removeBody" accesskey="&nntpRemoveMsgBody.accesskey;"
+ label="&nntpRemoveMsgBody.label;" oncommand="onCheckItem('nntp.removeBodyMin','nntp.removeBody');"/>
<textbox wsm_persist="true" id="nntp.removeBodyMin" size="2" value="30"
type="number" min="1"
aria-labelledby="nntp.removeBody nntp.removeBodyMin daysOldMsg"/>
<label value="&daysOld.label;" control="nntp.removeBodyMin" id="daysOldMsg"/>
</hbox>
</vbox>
</groupbox>
</page>
--- a/mailnews/base/public/nsIMsgCopyService.idl
+++ b/mailnews/base/public/nsIMsgCopyService.idl
@@ -89,21 +89,22 @@ interface nsIMsgCopyService : nsISupport
in nsIMsgWindow msgWindow);
/**
* Copies message in rfc format from file to folder.
*
* @param aFile A file which contains message in rfc format which
* will copied to destFolder.
* @param dstFolder Destination folder where a message will be copied.
- * @param msgToReplace Header which identifies message which will be
- * replaced by newly copied message or null. It is
- * used generally for drafts, when saving a new copy
- * of a draft message should replace the old
- * draft message.
+ * @param msgToReplace Header which identifies a message to use as a source
+ * of message properties, or null. For example, when
+ * deleting an attachment, the processed message is
+ * stored in a file, but the metadata should be copied
+ * from the original message. This method will NOT delete
+ * the original message.
* @param isDraftOrTemplate Specifies whether a message is a stored in draft
* folder or not. If is true listener should
* implement GetMessageId and return unique id for
* message in destination folder. This is important
* for IMAP servers which doesn't support uidplus.
* If destination folder contains message with the
* same message-id then it is possible that listener
* get wrong message key in callback
--- a/mailnews/base/public/nsIMsgDBView.idl
+++ b/mailnews/base/public/nsIMsgDBView.idl
@@ -41,16 +41,17 @@
interface nsIMsgFolder;
interface nsIMsgWindow;
interface nsIMessenger;
interface nsIMsgDBHdr;
interface nsIMsgThread;
interface nsIMsgDBViewCommandUpdater;
interface nsIMsgDatabase;
interface nsIMsgSearchSession;
+interface nsIMutableArray;
interface nsISimpleEnumerator;
interface nsITreeView;
interface nsIMsgCustomColumnHandler;
typedef long nsMsgViewNotificationCodeValue;
typedef long nsMsgViewCommandCheckStateValue;
typedef long nsMsgViewCommandTypeValue;
typedef long nsMsgNavigationTypeValue;
@@ -240,17 +241,17 @@ interface nsMsgNavigationType
const nsMsgNavigationTypeValue previousFlagged = 19;
const nsMsgNavigationTypeValue firstNew = 20;
const nsMsgNavigationTypeValue editUndo = 21;
const nsMsgNavigationTypeValue editRedo = 22;
const nsMsgNavigationTypeValue toggleSubthreadKilled = 23;
};
-[scriptable, uuid(03969356-7a74-4b78-bb17-0d56849c789f)]
+[scriptable, uuid(7b6f1f8f-f27a-409b-955b-ab40d46194e1)]
interface nsIMsgDBView : nsISupports
{
void open(in nsIMsgFolder folder, in nsMsgViewSortTypeValue sortType, in nsMsgViewSortOrderValue sortOrder, in nsMsgViewFlagsTypeValue viewFlags, out long count);
void openWithHdrs(in nsISimpleEnumerator aHeaders, in nsMsgViewSortTypeValue aSortType,
in nsMsgViewSortOrderValue aSortOrder,
in nsMsgViewFlagsTypeValue aViewFlags, out long aCount);
void close();
@@ -320,27 +321,50 @@ interface nsIMsgDBView : nsISupports
* @return - msg hdr at the passed in index
* @exception - NS_MSG_INVALID_DBVIEW_INDEX
*/
nsIMsgDBHdr getMsgHdrAt(in nsMsgViewIndex aIndex);
nsIMsgFolder getFolderForViewIndex(in nsMsgViewIndex index); // mainly for search
ACString getURIForViewIndex(in nsMsgViewIndex index);
nsIMsgDBView cloneDBView(in nsIMessenger aMessengerInstance, in nsIMsgWindow aMsgWindow, in nsIMsgDBViewCommandUpdater aCommandUpdater);
+
+ /**
+ * Provides a list of the message headers for the currently selected messages.
+ * If the "mail.operate_on_msgs_in_collapsed_threads" preference is enabled,
+ * then any collapsed thread roots that are selected will also (conceptually)
+ * have all of the messages in that thread selected and they will be included
+ * in the returned list.
+ *
+ * If the user has right-clicked on a message, this will return that message
+ * (and any collapsed children if so enabled) and not the selection prior to
+ * the right-click.
+ *
+ * @return an nsIMutableArray containing the selected message headers. You
+ * are free to mutate the array; it will not affect the underlying
+ * selection.
+ */
+ nsIMutableArray getMsgHdrsForSelection();
void getURIsForSelection(out unsigned long count, [retval, array, size_is(count)] out string uris);
void getIndicesForSelection(out unsigned long count, [retval, array, size_is(count)] out nsMsgViewIndex indices);
readonly attribute ACString URIForFirstSelectedMessage;
readonly attribute nsIMsgDBHdr hdrForFirstSelectedMessage;
void loadMessageByMsgKey(in nsMsgKey aMsgKey);
void loadMessageByViewIndex(in nsMsgViewIndex aIndex);
void loadMessageByUrl(in string aUrl);
void reloadMessage();
void reloadMessageWithAllParts();
+ /**
+ * The number of selected messages. If the
+ * "mail.operate_on_msgs_in_collapsed_threads" preference is enabled, then
+ * any collapsed thread roots that are selected will also conceptually have
+ * all of the messages in that thread selected.
+ */
readonly attribute unsigned long numSelected;
readonly attribute nsMsgViewIndex msgToSelectAfterDelete;
readonly attribute nsMsgViewIndex currentlyDisplayedMessage;
// used by "go to folder" feature
// and "remember last selected message" feature
// if key is not found, we don't select.
void selectMsgByKey(in nsMsgKey key);
--- a/mailnews/base/public/nsIMsgFolder.idl
+++ b/mailnews/base/public/nsIMsgFolder.idl
@@ -62,17 +62,17 @@ interface nsIMsgIdentity;
interface nsIArray;
interface nsIMutableArray;
interface nsISupportsArray;
typedef long nsMsgBiffState;
// enumerated type for determining if a message has been replied to, forwarded, etc.
typedef long nsMsgDispositionState;
-[scriptable, uuid(b2ea737c-f9ff-4431-984e-babcdff15f6f)]
+[scriptable, uuid(2848f8f2-8af5-4a4e-b095-cb8f30aada94)]
interface nsIMsgFolder : nsISupports {
const nsMsgBiffState nsMsgBiffState_NewMail = 0; // User has new mail waiting.
const nsMsgBiffState nsMsgBiffState_NoMail = 1; // No new mail is waiting.
const nsMsgBiffState nsMsgBiffState_Unknown = 2; // We dunno whether there is new mail.
/// Returns an enumerator containing the messages within the current database.
readonly attribute nsISimpleEnumerator messages;
@@ -127,22 +127,61 @@ interface nsIMsgFolder : nsISupports {
readonly attribute boolean canCompact;
/**
* the phantom server folder
*/
readonly attribute nsIMsgFolder rootFolder;
/**
- * function to get the filter list on folder's server
- * (or in the case of news, the filter list for this newsgroup)'
+ * Get the server's list of filters. (Or in the case of news, the
+ * filter list for this newsgroup)
+ * This list SHOULD be used for all incoming messages.
+ *
+ * Since the returned nsIMsgFilterList is mutable, it is not necessary to call
+ * setFilterList after the filters have been changed.
+ *
+ * @param aMsgWindow @ref msgwindow "The standard message window"
+ * @return The list of filters
*/
nsIMsgFilterList getFilterList(in nsIMsgWindow msgWindow);
+
+ /**
+ * Set the server's list of filters.
+ *
+ * Note that this does not persist the filter list. To change the contents
+ * of the existing filters, use getFilterList and mutate the values as
+ * appopriate.
+ *
+ * @param aFilterList The new list of filters.
+ */
void setFilterList(in nsIMsgFilterList filterList);
+ /**
+ * Get user editable filter list. This does not have to be the same as
+ * the filterlist above, typically depending on the users preferences.
+ * The filters in this list are not processed, but only to be edited by
+ * the user.
+ * @see getFilterList
+ *
+ * @param aMsgWindow @ref msgwindow "The standard message window"
+ * @return The list of filters
+ */
+ nsIMsgFilterList getEditableFilterList(in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Set user editable filter list.
+ * This does not persist the filterlist, @see setFilterList
+ * @see getEditableFilterList
+ * @see setFilterList
+ *
+ * @param aFilterList The new list of filters.
+ */
+ void setEditableFilterList(in nsIMsgFilterList aFilterList);
+
void ForceDBClosed ();
/**
* Close and backup a folder database prior to reparsing
*
* @param newName New name of the corresponding message folder.
* Used in rename to set the file name to match the renamed
* folder. Set to empty to use the existing folder name.
*/
--- a/mailnews/base/public/nsIMsgIncomingServer.idl
+++ b/mailnews/base/public/nsIMsgIncomingServer.idl
@@ -54,17 +54,17 @@ interface nsILocalFile;
interface nsIURI;
/*
* Interface for incoming mail/news host
* this is the base interface for all mail server types (imap, pop, nntp, etc)
* often you will want to add extra interfaces that give you server-specific
* attributes and methods.
*/
-[scriptable, uuid(ab7d1c04-2cad-4b51-b4cd-f151f1f1db9a)]
+[scriptable, uuid(0af3f1fd-a290-41ff-b0ba-266a8797627f)]
interface nsIMsgIncomingServer : nsISupports {
/**
* internal pref key - guaranteed to be unique across all servers
*/
attribute ACString key;
/**
@@ -189,22 +189,64 @@ interface nsIMsgIncomingServer : nsISupp
attribute boolean logonFallback;
/* dead code. */
readonly attribute boolean isSecureServer;
/* empty trash on exit */
attribute boolean emptyTrashOnExit;
- /* get filter list */
+ /**
+ * Get the server's list of filters.
+ *
+ * This SHOULD be the same filter list as the root folder's, if the server
+ * supports per-folder filters. Furthermore, this list SHOULD be used for all
+ * incoming messages.
+ *
+ * Since the returned nsIMsgFilterList is mutable, it is not necessary to call
+ * setFilterList after the filters have been changed.
+ *
+ * @param aMsgWindow @ref msgwindow "The standard message window"
+ * @return The list of filters.
+ */
nsIMsgFilterList getFilterList(in nsIMsgWindow aMsgWindow);
- /* set filter list */
+ /**
+ * Set the server's list of filters.
+ *
+ * Note that this does not persist the filter list. To change the contents
+ * of the existing filters, use getFilterList and mutate the values as
+ * appopriate.
+ *
+ * @param aFilterList The new list of filters.
+ */
void setFilterList(in nsIMsgFilterList aFilterList);
+ /**
+ * Get user editable filter list. This does not have to be the same as
+ * the filterlist above, typically depending on the users preferences.
+ * The filters in this list are not processed, but only to be edited by
+ * the user.
+ * @see getFilterList
+ *
+ * @param aMsgWindow @ref msgwindow "The standard message window"
+ * @return The list of filters.
+ */
+ nsIMsgFilterList getEditableFilterList(in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Set user editable filter list.
+ * This does not persist the filterlist, @see setFilterList
+ * @see getEditableFilterList
+ * @see setFilterList
+ *
+ * @param aFilterList The new list of filters.
+ */
+ void setEditableFilterList(in nsIMsgFilterList aFilterList);
+
/* we use this to set the default local path. we use this when migrating prefs */
void setDefaultLocalPath(in nsILocalFile aDefaultLocalPath);
/**
* Verify that we can logon
*
* @param aUrlListener - gets called back with success or failure.
* @param aMsgWindow nsIMsgWindow to use for notification callbacks.
--- a/mailnews/base/public/nsIMsgMailNewsUrl.idl
+++ b/mailnews/base/public/nsIMsgMailNewsUrl.idl
@@ -48,17 +48,17 @@ interface nsIMsgSearchSession;
interface nsICacheEntryDescriptor;
interface nsICacheSession;
interface nsIMimeHeaders;
interface nsIStreamListener;
interface nsIMsgFolder;
interface nsIMsgHeaderSink;
interface nsIMsgDBHdr;
-[scriptable, uuid(2FB327C2-00A3-4e3f-8CD6-93BED40BD621)]
+[scriptable, uuid(01850820-e382-4b0f-a109-23d69280876b)]
interface nsIMsgMailNewsUrl : nsIURL {
///////////////////////////////////////////////////////////////////////////////
// Eventually we'd like to push this type of functionality up into nsIURI.
// The idea is to allow the "application" (the part of the code which wants to
// run a url in order to perform some action) to register itself as a listener
// on url. As a url listener, the app will be informed when the url begins to run
// and when the url is finished.
////////////////////////////////////////////////////////////////////////////////
@@ -80,16 +80,22 @@ interface nsIMsgMailNewsUrl : nsIURL {
* @exception NS_ERROR_FAILURE May be thrown if the url does not
* relate to a folder, e.g. standalone
* .eml messages.
*/
attribute nsIMsgFolder folder;
attribute nsIMsgStatusFeedback statusFeedback;
+ /**
+ * The maximum progress for this URL. This might be a count, or it might
+ * be a number of bytes. A value of -1 indicates that this is unknown.
+ */
+ attribute long long maxProgress;
+
attribute nsIMsgWindow msgWindow;
// current mime headers if reading message
attribute nsIMimeHeaders mimeHeaders;
// the load group is computed from the msgWindow
readonly attribute nsILoadGroup loadGroup;
--- a/mailnews/base/public/nsIMsgProgress.idl
+++ b/mailnews/base/public/nsIMsgProgress.idl
@@ -38,17 +38,17 @@
#include "nsISupports.idl"
#include "domstubs.idl"
#include "nsIPrompt.idl"
#include "nsIWebProgressListener.idl"
interface nsIDOMWindowInternal;
interface nsIMsgWindow;
-[scriptable, uuid(892A9E47-853A-454A-8B02-94A7521D046D)]
+[scriptable, uuid(5aae7f1b-95f1-422e-86d5-2b1d599e2ccf)]
interface nsIMsgProgress: nsIWebProgressListener {
/**
* Open the progress dialog, you can specify parameters through an xpcom object
*/
void openProgressDialog(in nsIDOMWindowInternal parent,
in nsIMsgWindow aMsgWindow,
in string dialogURL,
@@ -59,18 +59,15 @@ interface nsIMsgProgress: nsIWebProgress
void closeProgressDialog(in boolean forceClose);
/* Register a Web Progress Listener */
void registerListener(in nsIWebProgressListener listener);
/* Unregister a Web Progress Listener */
void unregisterListener(in nsIWebProgressListener listener);
- /* Retrieve the prompter, needed to display modal dialog on top of progress dialog */
- nsIPrompt getPrompter();
-
/* Indicated if the user asked to cancel the current process */
attribute boolean processCanceledByUser;
attribute nsIMsgWindow msgWindow;
};
--- a/mailnews/base/resources/content/folderProps.xul
+++ b/mailnews/base/resources/content/folderProps.xul
@@ -121,18 +121,18 @@
label="&retentionUseDefault.label;" checked="true" oncommand="onUseDefaultRetentionSettings()"/>
</hbox>
<vbox class="indent">
<hbox class="indent">
<radiogroup wsm_persist="true" id="retention.keepMsg" aria-labelledby="retention.useDefault">
<radio wsm_persist="true" value="1" accesskey="&retentionKeepAll.accesskey;"
label="&retentionKeepAll.label;" oncommand="onCheckKeepMsg();"/>
<hbox flex="1" align="center">
- <radio wsm_persist="true" id="keepNewMsg" accesskey="&retentionKeepNew.accesskey;"
- value="3" label="&retentionKeepNew.label;" oncommand="onCheckKeepMsg();"/>
+ <radio wsm_persist="true" id="keepNewMsg" accesskey="&retentionKeepRecent.accesskey;"
+ value="3" label="&retentionKeepRecent.label;" oncommand="onCheckKeepMsg();"/>
<textbox wsm_persist="true" id="retention.keepNewMsgMin"
type="number" min="1" increment="10" size="4" value="2000"
aria-labelledby="keepNewMsg retention.keepNewMsgMin retention.keepNewMsgMinLabel"/>
<label value="&message.label;" control="retention.keepNewMsgMin" id="retention.keepNewMsgMinLabel"/>
</hbox>
<hbox flex="1" align="center">
<radio wsm_persist="true" id="keepMsg" accesskey="&retentionDeleteMsg.accesskey;"
value="2" label="&retentionDeleteMsg.label;" oncommand="onCheckKeepMsg();"/>
@@ -141,18 +141,18 @@
aria-labelledby="keepMsg retention.keepOldMsgMin retention.keepOldMsgMinLabel"/>
<label value="&daysOld.label;" control="retention.keepOldMsgMin" id="retention.keepOldMsgMinLabel"/>
</hbox>
</radiogroup>
</hbox>
<hbox class="indent">
<vbox>
<checkbox id="retention.keepUnread" wsm_persist="true"
- label="&retentionKeepUnread.label;"
- accesskey="&retentionKeepUnread.accesskey;"
+ label="&retentionKeepUnreadHidden.label;"
+ class="keepUnreadOnly" hidden="true"
observes="retention.keepMsg" checked="false"/>
<checkbox id="retention.applyToFlagged" wsm_persist="true"
label="&retentionApplyToFlagged.label;"
accesskey="&retentionApplyToFlagged.accesskey;"
observes="retention.keepMsg" checked="true"/>
</vbox>
</hbox>
</vbox>
--- a/mailnews/base/resources/content/virtualFolderProperties.js
+++ b/mailnews/base/resources/content/virtualFolderProperties.js
@@ -38,41 +38,46 @@
* ***** END LICENSE BLOCK ***** */
var gPickedFolder;
var gMailView = null;
var msgWindow; // important, don't change the name of this variable. it's really a global used by commandglue.js
var gSearchTermSession; // really an in memory temporary filter we use to read in and write out the search terms
var gSearchFolderURIs = "";
+var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
+
+Components.utils.import("resource://app/modules/virtualFolderWrapper.js");
+Components.utils.import("resource://app/modules/iteratorUtils.jsm");
+
function onLoad()
{
var arguments = window.arguments[0];
document.getElementById("name").focus();
// call this when OK is pressed
msgWindow = arguments.msgWindow;
initializeSearchWidgets();
setSearchScope(nsMsgSearchScope.offlineMail);
if (arguments.editExistingFolder)
- InitDialogWithVirtualFolder(arguments.folder ? arguments.folder.URI : null);
+ InitDialogWithVirtualFolder(arguments.folder);
else // we are creating a new virtual folder
{
// it is possible that we were given arguments to pre-fill the dialog with...
gSearchTermSession = Components.classes["@mozilla.org/messenger/searchSession;1"]
.createInstance(Components.interfaces.nsIMsgSearchSession);
if (arguments.searchTerms) // then add them to our search session
{
- var count = arguments.searchTerms.Count();
- for (var searchIndex = 0; searchIndex < count; )
- gSearchTermSession.appendTerm(arguments.searchTerms.QueryElementAt(searchIndex++, Components.interfaces.nsIMsgSearchTerm));
+ for each (let searchTerm in fixIterator(arguments.searchTerms,
+ Components.interfaces.nsIMsgSearchTerm))
+ gSearchTermSession.appendTerm(searchTerm);
}
if (arguments.folder)
{
// pre select the folderPicker, based on what they selected in the folder pane
gPickedFolder = arguments.folder;
try {
document.getElementById("msgNewFolderPopup").selectFolder(arguments.folder);
} catch(ex) {
@@ -121,44 +126,39 @@ function updateOnlineSearchState()
checkbox.removeAttribute('disabled');
else
{
checkbox.setAttribute('disabled', true);
checkbox.checked = false;
}
}
-function InitDialogWithVirtualFolder(aVirtualFolderURI)
+function InitDialogWithVirtualFolder(aVirtualFolder)
{
+ let virtualFolderWrapper =
+ VirtualFolderHelper.wrapVirtualFolder(window.arguments[0].folder);
+
// when editing an existing folder, hide the folder picker that stores the parent location of the folder
document.getElementById("chooseFolderLocationRow").collapsed = true;
var folderNameField = document.getElementById("name");
folderNameField.disabled = true;
- var msgFolder = GetMsgFolderFromUri(aVirtualFolderURI);
- var dbFolderInfo = msgFolder.msgDatabase.dBFolderInfo;
-
- gSearchFolderURIs = dbFolderInfo.getCharProperty("searchFolderUri");
- var searchTermString = dbFolderInfo.getCharProperty("searchStr");
- document.getElementById('searchOnline').checked = dbFolderInfo.getBooleanProperty("searchOnline", false);
-
- // work around to get our search term string converted into a real array of search terms
- var filterService = Components.classes["@mozilla.org/messenger/services/filters;1"].getService(Components.interfaces.nsIMsgFilterService);
- var filterList = filterService.getTempFilterList(msgFolder);
- gSearchTermSession = filterList.createFilter("temp");
- filterList.parseCondition(gSearchTermSession, searchTermString);
+ gSearchFolderURIs = virtualFolderWrapper.searchFolderURIs;
+ document.getElementById('searchOnline').checked = virtualFolderWrapper.onlineSearch;
+ gSearchTermSession = virtualFolderWrapper.searchTermsSession;
setupSearchRows(gSearchTermSession.searchTerms);
// set the name of the folder
- folderNameField.value = msgFolder.prettyName;
+ folderNameField.value = aVirtualFolder.prettyName;
// update the window title based on the name of the saved search
var messengerBundle = document.getElementById("bundle_messenger");
- document.title = messengerBundle.getFormattedString('editVirtualFolderPropertiesTitle', [msgFolder.prettyName]);
+ document.title = messengerBundle.getFormattedString('editVirtualFolderPropertiesTitle',
+ [aVirtualFolder.prettyName]);
}
function onFolderPick(aEvent) {
gPickedFolder = aEvent.target._folder;
document.getElementById("msgNewFolderPicker")
.setAttribute("label", gPickedFolder.prettyName);
}
@@ -177,34 +177,29 @@ function onOK()
messengerBundle.getString('alertNoSearchFoldersSelected'));
return false;
}
if (window.arguments[0].editExistingFolder)
{
// update the search terms
saveSearchTerms(gSearchTermSession.searchTerms, gSearchTermSession);
- var searchTermString = getSearchTermString(gSearchTermSession.searchTerms);
-
- var msgFolder = window.arguments[0].folder;
- var msgDatabase = msgFolder.msgDatabase;
- var dbFolderInfo = msgDatabase.dBFolderInfo;
-
- // set the view string as a property of the db folder info
- // set the original folder name as well.
- dbFolderInfo.setCharProperty("searchStr", searchTermString);
- dbFolderInfo.setCharProperty("searchFolderUri", gSearchFolderURIs);
- dbFolderInfo.setBooleanProperty("searchOnline", searchOnline);
- msgDatabase.Close(true);
+ // save the settings
+ let virtualFolderWrapper =
+ VirtualFolderHelper.wrapVirtualFolder(window.arguments[0].folder);
+ virtualFolderWrapper.searchTerms = gSearchTermSession.searchTerms;
+ virtualFolderWrapper.searchFolders = gSearchFolderURIs;
+ virtualFolderWrapper.onlineSearch = searchOnline;
+ virtualFolderWrapper.cleanUpMessageDatabase();
var accountManager = Components.classes["@mozilla.org/messenger/account-manager;1"].getService(Components.interfaces.nsIMsgAccountManager);
accountManager.saveVirtualFolders();
if (window.arguments[0].onOKCallback)
- window.arguments[0].onOKCallback(msgFolder.URI);
+ window.arguments[0].onOKCallback(virtualFolderWrapper.virtualFolder.URI);
return true;
}
var uri = gPickedFolder.URI;
if (name && uri) // create a new virtual folder
{
// check to see if we already have a folder with the same name and alert the user if so...
var parentFolder = GetMsgFolderFromUri(uri);
@@ -225,17 +220,19 @@ function onOK()
Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
promptService.alert(window, null,
messengerBundle.getString('folderExists'));
return false;
}
saveSearchTerms(gSearchTermSession.searchTerms, gSearchTermSession);
- CreateVirtualFolder(name, parentFolder, gSearchFolderURIs, gSearchTermSession.searchTerms, searchOnline);
+ VirtualFolderHelper.createNewVirtualFolder(name, parentFolder, gSearchFolderURIs,
+ gSearchTermSession.searchTerms,
+ searchOnline);
}
return true;
}
function onCancel()
{
// close the window
--- a/mailnews/base/resources/content/virtualFolderProperties.xul
+++ b/mailnews/base/resources/content/virtualFolderProperties.xul
@@ -52,17 +52,18 @@
]>
<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="virtualFolderPropertiesDialog"
title="&virtualFolderProperties.title;"
onload="onLoad();"
buttons="accept,cancel"
style="width: 50em; height: 28em;"
- ondialogaccept="return onOK();">
+ windowtype="mailnews:virtualFolderProperties"
+ ondialogaccept="return onOK();">
<stringbundleset id="stringbundleset">
<stringbundle id="bundle_search" src="chrome://messenger/locale/search.properties"/>
<stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
</stringbundleset>
<script type="application/x-javascript" src="chrome://messenger/content/mailCommands.js"/>
<script type="application/x-javascript" src="chrome://messenger/content/commandglue.js"/>
--- a/mailnews/base/search/src/nsMsgSearchAdapter.cpp
+++ b/mailnews/base/search/src/nsMsgSearchAdapter.cpp
@@ -714,19 +714,23 @@ nsresult nsMsgSearchAdapter::EncodeImap
// create our expression
nsMsgSearchBoolExpression * expression = new nsMsgSearchBoolExpression();
if (!expression)
return NS_ERROR_OUT_OF_MEMORY;
for (i = 0; i < termCount && NS_SUCCEEDED(err); i++)
{
char *termEncoding;
+ PRBool matchAll;
nsCOMPtr<nsIMsgSearchTerm> pTerm;
searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm),
(void **)getter_AddRefs(pTerm));
+ pTerm->GetMatchAll(&matchAll);
+ if (matchAll)
+ continue;
err = EncodeImapTerm (pTerm, reallyDredd, srcCharset, destCharset, &termEncoding);
if (NS_SUCCEEDED(err) && nsnull != termEncoding)
{
expression = nsMsgSearchBoolExpression::AddSearchTerm(expression, pTerm, termEncoding);
delete [] termEncoding;
}
}
@@ -1078,54 +1082,54 @@ NS_IMETHODIMP nsMsgSearchValidityManager
// see search-attributes.properties
struct
{
nsMsgSearchAttribValue id;
const char* property;
}
nsMsgSearchAttribMap[] =
{
- nsMsgSearchAttrib::Subject, "Subject",
- nsMsgSearchAttrib::Sender, "From",
- nsMsgSearchAttrib::Body, "Body",
- nsMsgSearchAttrib::Date, "Date",
- nsMsgSearchAttrib::Priority, "Priority",
- nsMsgSearchAttrib::MsgStatus, "Status",
- nsMsgSearchAttrib::To, "To",
- nsMsgSearchAttrib::CC, "Cc",
- nsMsgSearchAttrib::ToOrCC, "ToOrCc",
- nsMsgSearchAttrib::AgeInDays, "AgeInDays",
- nsMsgSearchAttrib::Size, "SizeKB",
- nsMsgSearchAttrib::Keywords, "Tags",
- nsMsgSearchAttrib::Name, "AnyName",
- nsMsgSearchAttrib::DisplayName, "DisplayName",
- nsMsgSearchAttrib::Nickname, "Nickname",
- nsMsgSearchAttrib::ScreenName, "ScreenName",
- nsMsgSearchAttrib::Email, "Email",
- nsMsgSearchAttrib::AdditionalEmail, "AdditionalEmail",
- nsMsgSearchAttrib::PhoneNumber, "AnyNumber",
- nsMsgSearchAttrib::WorkPhone, "WorkPhone",
- nsMsgSearchAttrib::HomePhone, "HomePhone",
- nsMsgSearchAttrib::Fax, "Fax",
- nsMsgSearchAttrib::Pager, "Pager",
- nsMsgSearchAttrib::Mobile, "Mobile",
- nsMsgSearchAttrib::City, "City",
- nsMsgSearchAttrib::Street, "Street",
- nsMsgSearchAttrib::Title, "Title",
- nsMsgSearchAttrib::Organization, "Organization",
- nsMsgSearchAttrib::Department, "Department",
- nsMsgSearchAttrib::AllAddresses, "FromToCcOrBcc",
- nsMsgSearchAttrib::JunkScoreOrigin, "JunkScoreOrigin",
- nsMsgSearchAttrib::JunkPercent, "JunkPercent",
- nsMsgSearchAttrib::HasAttachmentStatus, "AttachmentStatus",
- nsMsgSearchAttrib::JunkStatus, "JunkStatus",
- nsMsgSearchAttrib::Label, "Label",
- nsMsgSearchAttrib::OtherHeader, "Customize",
+ {nsMsgSearchAttrib::Subject, "Subject"},
+ {nsMsgSearchAttrib::Sender, "From"},
+ {nsMsgSearchAttrib::Body, "Body"},
+ {nsMsgSearchAttrib::Date, "Date"},
+ {nsMsgSearchAttrib::Priority, "Priority"},
+ {nsMsgSearchAttrib::MsgStatus, "Status"},
+ {nsMsgSearchAttrib::To, "To"},
+ {nsMsgSearchAttrib::CC, "Cc"},
+ {nsMsgSearchAttrib::ToOrCC, "ToOrCc"},
+ {nsMsgSearchAttrib::AgeInDays, "AgeInDays"},
+ {nsMsgSearchAttrib::Size, "SizeKB"},
+ {nsMsgSearchAttrib::Keywords, "Tags"},
+ {nsMsgSearchAttrib::Name, "AnyName"},
+ {nsMsgSearchAttrib::DisplayName, "DisplayName"},
+ {nsMsgSearchAttrib::Nickname, "Nickname"},
+ {nsMsgSearchAttrib::ScreenName, "ScreenName"},
+ {nsMsgSearchAttrib::Email, "Email"},
+ {nsMsgSearchAttrib::AdditionalEmail, "AdditionalEmail"},
+ {nsMsgSearchAttrib::PhoneNumber, "AnyNumber"},
+ {nsMsgSearchAttrib::WorkPhone, "WorkPhone"},
+ {nsMsgSearchAttrib::HomePhone, "HomePhone"},
+ {nsMsgSearchAttrib::Fax, "Fax"},
+ {nsMsgSearchAttrib::Pager, "Pager"},
+ {nsMsgSearchAttrib::Mobile, "Mobile"},
+ {nsMsgSearchAttrib::City, "City"},
+ {nsMsgSearchAttrib::Street, "Street"},
+ {nsMsgSearchAttrib::Title, "Title"},
+ {nsMsgSearchAttrib::Organization, "Organization"},
+ {nsMsgSearchAttrib::Department, "Department"},
+ {nsMsgSearchAttrib::AllAddresses, "FromToCcOrBcc"},
+ {nsMsgSearchAttrib::JunkScoreOrigin, "JunkScoreOrigin"},
+ {nsMsgSearchAttrib::JunkPercent, "JunkPercent"},
+ {nsMsgSearchAttrib::HasAttachmentStatus, "AttachmentStatus"},
+ {nsMsgSearchAttrib::JunkStatus, "JunkStatus"},
+ {nsMsgSearchAttrib::Label, "Label"},
+ {nsMsgSearchAttrib::OtherHeader, "Customize"},
// the last id is -1 to denote end of table
- -1, ""
+ {-1, ""}
};
NS_IMETHODIMP
nsMsgSearchValidityManager::GetAttributeProperty(nsMsgSearchAttribValue aSearchAttribute,
nsAString& aProperty)
{
for (PRInt32 i = 0; nsMsgSearchAttribMap[i].id >= 0; ++i)
{
--- a/mailnews/base/search/src/nsMsgSearchTerm.cpp
+++ b/mailnews/base/search/src/nsMsgSearchTerm.cpp
@@ -788,28 +788,28 @@ nsresult nsMsgSearchTerm::MatchArbitrary
// strip trailing white space
char * end = buf_end - 1;
while (end > headerValue && isspace(*end)) // while we haven't gone back past the start and we are white space....
{
*end = '\0'; // eat up the white space
end--; // move back and examine the previous character....
}
- if (headerValue < buf_end && *headerValue) // make sure buf has info besides just the header
+ // Make sure buf has info besides just the header.
+ // Otherwise, it's just an empty header.
+ if (headerValue < buf_end && *headerValue)
{
PRBool result2;
err = MatchRfc2047String(headerValue, charset, charsetOverride, &result2); // match value with the other info...
if (result != result2) // if we found a match
{
searchingHeaders = PR_FALSE; // then stop examining the headers
result = result2;
}
}
- else
- NS_ASSERTION(PR_FALSE, "error matching arbitrary headers"); // mscott --> i'd be curious if there is a case where this fails....
}
if (EMPTY_MESSAGE_LINE(buf))
searchingHeaders = PR_FALSE;
}
delete bodyHandler;
*pResult = result;
return err;
}
--- a/mailnews/base/src/dbViewWrapper.js
+++ b/mailnews/base/src/dbViewWrapper.js
@@ -45,16 +45,20 @@ const Cu = Components.utils;
Cu.import("resource://app/modules/mailViewManager.js");
Cu.import("resource://app/modules/searchSpec.js");
Cu.import("resource://app/modules/virtualFolderWrapper.js");
const nsMsgFolderFlags = Ci.nsMsgFolderFlags;
const nsMsgViewType = Ci.nsMsgViewType;
const nsMsgViewFlagsType = Ci.nsMsgViewFlagsType;
const nsMsgViewSortType = Ci.nsMsgViewSortType;
+const nsMsgViewSortOrder = Ci.nsMsgViewSortOrder;
+const nsMsgMessageFlags = Ci.nsMsgMessageFlags;
+
+const MSG_VIEW_FLAG_DUMMY = 0x20000000;
/**
* Helper singleton for DBViewWrapper that tells instances when something
* interesting is happening to the folder(s) they care about. The rationale
* for this is to:
* - reduce listener overhead (although arguably the events we listen to are
* fairly rare)
* - make testing / verification easier by centralizing and exposing listeners.
@@ -75,27 +79,23 @@ var FolderNotificationHelper = {
_interestedWrappers: {},
/**
* Initialize our listeners. We currently don't bother cleaning these up
* because we are a singleton and if anyone imports us, they probably want
* us for as long as their application so shall live.
*/
_init: function FolderNotificationHelper__init() {
- let atomService =
- Cc["@mozilla.org/atom-service;1"]
- .getService(Ci.nsIAtomService);
- this._kFolderLoadedAtom = atomService.getAtom("FolderLoaded");
-
// register with the session for our folded loaded notifications
let mailSession =
Cc["@mozilla.org/messenger/services/session;1"]
.getService(Ci.nsIMsgMailSession);
mailSession.AddFolderListener(this,
- Ci.nsIFolderListener.event);
+ Ci.nsIFolderListener.event |
+ Ci.nsIFolderListener.intPropertyChanged);
// register with the notification service for deleted folder notifications
let notificationService =
Cc["@mozilla.org/messenger/msgnotificationservice;1"]
.getService(Ci.nsIMsgFolderNotificationService);
notificationService.addListener(this,
Ci.nsIMsgFolderNotificationService.folderDeleted |
// we need to track renames because we key off of URIs. frick.
@@ -138,17 +138,17 @@ var FolderNotificationHelper = {
* @param aNotherFolder A folder that may or may not be in aFolders.
* @param aViewWrapper The view wrapper that is up to no good.
*/
stalkFolders: function FolderNotificationHelper_stalkFolders(
aFolders, aNotherFolder, aViewWrapper) {
let folders = aFolders.concat();
if (aNotherFolder && folders.indexOf(aNotherFolder) == -1)
folders.push(aNotherFolder);
- for each (let [, folder] in Iterator(aFolders)) {
+ for each (let [, folder] in Iterator(folders)) {
let wrappers = this._interestedWrappers[folder.URI];
if (wrappers == null)
wrappers = this._interestedWrappers[folder.URI] = [];
wrappers.push(aViewWrapper);
}
},
/**
@@ -195,38 +195,72 @@ var FolderNotificationHelper = {
for each (let [folderURI, wrappers] in
Iterator(this._interestedWrappers)) {
return true;
}
return false;
},
/* ***** Notifications ***** */
+ _notifyHelper: function FolderNotificationHelper__notifyHelper(aFolder,
+ aHandlerName) {
+ let wrappers = this._interestedWrappers[aFolder.URI];
+ if (wrappers) {
+ // clone the list to avoid confusing mutation by listeners
+ for each (let [, wrapper] in Iterator(wrappers.concat())) {
+ wrapper[aHandlerName](aFolder);
+ }
+ }
+ },
OnItemEvent: function FolderNotificationHelper_OnItemEvent(
aFolder, aEvent) {
- if (aEvent == this._kFolderLoadedAtom) {
+ let eventType = aEvent.toString();
+ if (eventType == "FolderLoaded") {
let folderURI = aFolder.URI;
let widgets = this._pendingFolderUriToViewWrapperLists[folderURI];
if (widgets) {
for each (let [, widget] in Iterator(widgets)) {
// we are friends, this is an explicit relationship.
// (we don't use a generic callback mechanism because the 'this' stuff
// gets ugly and no one else should be hooking in at this level.)
try {
widget._folderLoaded(aFolder);
}
catch (ex) {
dump("``` EXCEPTION DURING NOTIFY: " + ex.fileName + ":" +
ex.lineNumber + ": " + ex + "\n");
+ if (ex.stack)
+ dump("STACK: " + ex.stack + "\n");
+ Cu.reportError(ex);
}
}
delete this._pendingFolderUriToViewWrapperLists[folderURI];
}
}
+ else if (eventType == "AboutToCompact") {
+ this._notifyHelper(aFolder, "_aboutToCompactFolder");
+ }
+ else if (eventType == "CompactCompleted") {
+ this._notifyHelper(aFolder, "_compactedFolder");
+ }
+ else if (eventType == "DeleteOrMoveMsgCompleted") {
+ this._notifyHelper(aFolder, "_deleteCompleted");
+ }
+ else if (eventType == "DeleteOrMoveMsgFailed") {
+ this._notifyHelper(aFolder, "_deleteFailed");
+ }
+
+ },
+
+ OnItemIntPropertyChanged: function(aFolder, aProperty, aOldValue, aNewValue) {
+ let propertyString = aProperty.toString();
+ if ((propertyString == "TotalMessages") ||
+ (propertyString == "TotalUnreadMessages"))
+ this._notifyHelper(aFolder, "_messageCountsChanged");
},
_folderMoveHelper: function(aOldFolder, aNewFolder) {
let oldURI = aOldFolder.URI;
let newURI = aNewFolder.URI;
// fix up our listener tables.
if (oldURI in this._pendingFolderUriToViewWrapperLists) {
this._pendingFolderUriToViewWrapperLists[newURI] =
@@ -271,17 +305,17 @@ var FolderNotificationHelper = {
folderDeleted: function FolderNotificationHelper_folderDeleted(aFolder) {
let wrappers = this._interestedWrappers[aFolder.URI];
if (wrappers) {
// clone the list to avoid confusing mutation by listeners
for each (let [, wrapper] in Iterator(wrappers.concat())) {
wrapper._folderDeleted(aFolder);
}
- // if the folder is deleted, it's not going to get deleted again.
+ // if the folder is deleted, it's not going to ever do anything again
delete this._interestedWrappers[aFolder.URI];
}
},
};
FolderNotificationHelper._init();
/**
@@ -364,64 +398,117 @@ IDBViewWrapperListener.prototype = {
* is fundamentally a UI issue. Additionally, because the MsgCreateDBView
* notification consumers assume gDBView whose exposure is affected by tabs,
* the tab logic needs to be involved.
*/
onCreatedView: function() {
},
/**
+ * This event is generated just before we close/destroy a message view.
+ *
+ * @param aFolderIsComingBack Indicates whether we are planning to create a
+ * new view to display the same folder after we destroy this view. This
+ * will be the case unless we are switching to display a new folder or
+ * closing the view wrapper entirely.
+ */
+ onDestroyingView: function(aFolderIsComingBack) {
+ },
+
+ /**
* Generated when the folder is being entered for display. This is the chance
* for the listener to affect any UI-related changes to the folder required.
* Currently, this just means setting the header cache size (which needs to
* be proportional to the number of lines in the tree view, and is thus a
* UI issue.)
*/
onDisplayingFolder: function() {
},
/**
+ * Generated when we are leaving a folder.
+ */
+ onLeavingFolder: function() {
+ },
+
+ /**
* Things to do once all the messages that should show up in a folder have
* shown up. For a real folder, this happens when the folder is entered.
* For a (multi-folder) virtual folder, this happens when the search
* completes.
*/
onAllMessagesLoaded: function() {
},
/**
* The mail view changed. The mail view widget is likely to care about this.
*/
- onMailViewChanged: function () {
+ onMailViewChanged: function() {
+ },
+ /**
+ * The active sort changed, and that is all that changed. If the sort is
+ * changing because the view is being destroyed and re-created, this event
+ * will not be generated.
+ */
+ onSortChanged: function() {
},
+ /**
+ * This event is generated when messages in one of the folders backing the
+ * view have been removed by message moves / deletion. If there is a search
+ * in effect, it is possible that the removed messages were not visible in
+ * the view in the first place.
+ */
+ onMessagesRemoved: function () {
+ },
+ /**
+ * Like onMessagesRemoved, but something went awry in the move/deletion and
+ * it failed. Although this is not a very interesting event on its own,
+ * it is useful in cases where the listener was expecting an
+ * onMessagesRemoved and might need to clean some state up.
+ */
+ onMessageRemovalFailed: function () {
+ },
+
+ /**
+ * The total message count or total unread message counts changed.
+ */
+ onMessageCountsChanged: function () {
+ },
};
/**
* Encapsulates everything related to working with our nsIMsgDBView
* implementations.
+ *
+ * Things we do not do and why we do not do them:
+ * - Selection. This depends on having an nsITreeSelection object and we choose
+ * to avoid entanglement with XUL/layout code. Selection accordingly must be
+ * handled a layer up in the FolderDisplayWidget.
*/
function DBViewWrapper(aListener) {
this.displayedFolder = null;
this.listener = aListener;
this._underlyingData = this.kUnderlyingNone;
this._underlyingFolders = null;
+ this._syntheticView = null;
this._viewUpdateDepth = 0;
this._mailViewIndex = MailViewConstants.kViewItemAll;
this._mailViewData = null;
this._specialView = null;
this._sort = [];
- this._viewFlags = 0;
+ // see the _viewFlags getter and setter for info on our use of __viewFlags.
+ this.__viewFlags = null;
this.dbView = null;
this.search = null;
this._folderLoading = false;
this._searching = false;
}
DBViewWrapper.prototype = {
@@ -436,108 +523,226 @@ DBViewWrapper.prototype = {
kUnderlyingRealFolder: 1,
/**
* The underlying data source is a virtual folder that is operating over
* multiple underlying folders.
*/
kUnderlyingMultipleFolder: 2,
/**
* Our data source is transient, most likely a gloda search that crammed the
- * results into us.
+ * results into us. This is different from a search view.
*/
kUnderlyingSynthetic: 3,
+ /**
+ * We are a search view, which translates into a search that has underlying
+ * folders, just like kUnderlyingMultipleFolder, but we have no
+ * displayedFolder. We differ from kUnderlyingSynthetic in that we are
+ * not just a bunch of message headers randomly crammed in.
+ */
+ kUnderlyingSearchView: 4,
/**
* @return true if the folder being displayed is backed by a single 'real'
* folder. This folder can be a saved search on that folder or just
* an outright un-filtered display of that folder.
*/
get isSingleFolder() {
return this._underlyingData == this.kUnderlyingRealFolder;
},
/**
* @return true if the folder being displayed is a virtual folder backed by
- * multiple 'real' folders. This corresponds to a cross-folder saved
- * search.
+ * multiple 'real' folders or a search view. This corresponds to a
+ * cross-folder saved search.
*/
get isMultiFolder() {
- return this._underlyingData == this.kUnderlyingMultipleFolder;
+ return (this._underlyingData == this.kUnderlyingMultipleFolder) ||
+ (this._underlyingData == this.kUnderlyingSearchView);;
},
/**
* @return true if the folder being displayed is not a real folder at all,
* but rather the result of an un-scoped search, such as a gloda search.
*/
get isSynthetic() {
return this._underlyingData == this.kUnderlyingSynthetic;
},
/**
+ * Check if the folder in question backs the currently displayed folder. For
+ * a virtual folder, this is a test of whether the virtual folder includes
+ * messages from the given folder. For a 'real' single folder, this is
+ * effectively a test against displayedFolder.
+ * If you want to see if the displayed folder is a folder, just compare
+ * against the displayedFolder attribute.
+ */
+ isUnderlyingFolder: function DBViewWrapper_isUnderlyingFolder(aFolder) {
+ for each (let [i,underlyingFolder] in Iterator(this._underlyingFolders)) {
+ if (aFolder == underlyingFolder)
+ return true;
+ }
+ return false;
+ },
+
+ /**
* Refresh the view by re-creating the view. You would do this to get rid of
* messages that no longer match the view but are kept around for view
* stability reasons. (In other words, in an unread-messages view, you would
* go insane if when you clicked on a message it immediately disappeared
* because it no longer matched.)
* This method was adding for testing purposes and does not have a (legacy) UI
* reason for existing. (The 'open' method is intended to behave identically
* to the legacy UI if you click on the currently displayed folder.)
*/
refresh: function DBViewWrapper_refresh() {
this._applyViewChanges();
},
/**
+ * Null out the folder's database to avoid memory bloat if we don't have a
+ * reason to keep the database around. Currently, we keep all Inboxes
+ * around and null out everyone else. This is a standard stopgap measure
+ * until we have something more clever going on.
+ * In general, there is little potential downside to nulling out the message
+ * database reference when it is in use. As long as someone is holding onto
+ * a message header from the database, the database will be kept open, and
+ * therefore the database service will still have a reference to the db.
+ * When the folder goes to ask for the database again, the service will have
+ * it, and it will not need to be re-opened.
+ *
+ * Another heuristic we could theoretically use is use the mail session's
+ * isFolderOpenInWindow call, except that uses the outmoded concept that each
+ * window will have at most one folder open. So nuts to that.
+ *
+ * Note: regrettably a unit test cannot verify that we did this; msgDatabase
+ * is a getter that will always try and load the message database!
+ */
+ _releaseFolderDatabase: function DBViewWrapper__nullFolderDatabase(aFolder) {
+ if (!this._isSpecialFolder(aFolder, nsMsgFolderFlags.Inbox, false))
+ aFolder.msgDatabase = null;
+ },
+
+ /**
+ * Clone this DBViewWrapper and its underlying nsIMsgDBView.
+ *
+ * @param aListener {IDBViewWrapperListener} The listener to use on the new view.
+ */
+ clone: function DBViewWrapper_clone(aListener) {
+ let doppel = new DBViewWrapper(aListener);
+
+ // -- copy attributes
+ doppel.displayedFolder = this.displayedFolder;
+ doppel._underlyingData = this._underlyingData;
+ doppel._underlyingFolders = this._underlyingFolders ?
+ this._underlyingFolders.concat() : null;
+ doppel._syntheticView = this._syntheticView;
+
+ // _viewUpdateDepth should stay at its initial value of zero
+ doppel._mailViewIndex = this._mailViewIndex;
+ doppel._mailViewData = this._mailViewData;
+
+ doppel._specialView = this._specialView;
+ // a shallow copy is all that is required for sort; we do not mutate entries
+ doppel._sort = this._sort.concat();
+
+ // -- register listeners...
+ // note: this does not get us a folder loaded notification. Our expected
+ // use case for cloning is displaying a single message already visible in
+ // the original view, which implies we don't need to hang about for folder
+ // loaded notification messages.
+ FolderNotificationHelper.stalkFolders(doppel._underlyingFolders,
+ doppel.displayedFolder,
+ doppel);
+
+ // -- clone the view
+ if (this.dbView)
+ doppel.dbView = this.dbView.cloneDBView(aListener.messenger,
+ aListener.msgWindow,
+ aListener.threadPaneCommandUpdater)
+ .QueryInterface(Components.interfaces.nsITreeView);
+ // -- clone the search
+ if (this.search)
+ doppel.search = this.search.clone(doppel);
+
+ return doppel;
+ },
+
+ /**
* Close the current view. You would only do this if you want to clean up all
* the resources associated with this view wrapper. You would not do this
* for UI reasons like the user de-selecting the node in the tree; we should
* always be displaying something when used in a UI context!
*
* @param aFolderIsDead If true, tells us not to try and tidy up on our way
* out by virtue of the fact that the folder is dead and should not be
* messed with.
*/
close: function DBViewWrapper_close(aFolderIsDead) {
- if (this._underlyingFolders)
- FolderNotificationHelper.removeNotifications(this._underlyingFolders,
- this);
-
if (this.displayedFolder != null) {
// onLeavingFolder does all the application-level stuff related to leaving
// the folder (marking as read, etc.) We only do this when the folder
// is not dead (for obvious reasons).
- if (!aFolderIsDead)
- this.onLeavingFolder();
+ if (!aFolderIsDead) {
+ // onLeavingFolder must be called before we potentially null out its
+ // msgDatabase, which we will do in the upcoming underlyingFolders loop
+ this.onLeavingFolder(); // application logic
+ this.listener.onLeavingFolder(); // display logic
+ }
+ // (potentially) zero out the display folder if we are dealing with a
+ // virtual folder and so the next loop won't take care of it.
+ if (this.isVirtual) {
+ FolderNotificationHelper.removeNotifications([this.displayedFolder],
+ this);
+ this._releaseFolderDatabase(this.displayedFolder);
+ }
+
+ this.folderLoading = false;
this.displayedFolder = null;
- this.folderLoading = false;
+ }
+
+ if (this._underlyingFolders) {
+ FolderNotificationHelper.removeNotifications(this._underlyingFolders,
+ this);
+ // (potentially) zero out the underlying msgDatabase references
+ for each (let [, folder] in Iterator(this._underlyingFolders))
+ this._releaseFolderDatabase(folder);
}
// kill off the view and its search association
if (this.dbView) {
+ this.listener.onDestroyingView(false);
this.search.dissociateView(this.dbView);
this.dbView.close();
this.dbView = null;
}
+ // zero out the view update depth here. We don't do it on open because it's
+ // theoretically be nice to be able to start a view update before you open
+ // something so you can defer the open. In practice, that is not yet
+ // tested.
+ this._viewUpdateDepth = 0;
+
this._underlyingData = this.kUnderlyingNone;
this._underlyingFolders = null;
+ this._syntheticView = null;
this._mailViewIndex = MailViewConstants.kViewItemAll;
this._mailViewData = null;
this._specialView = null;
this._sort = [];
- this._viewFlags = 0;
+ this.__viewFlags = null;
this.search = null;
},
/**
- * Open the passed-in folder.
+ * Open the passed-in nsIMsgFolder folder. Use openSynthetic for synthetic
+ * view providers.
*/
open: function DBViewWrapper_open(aFolder) {
if (aFolder == null) {
this.close();
return;
}
// If we are in the same folder, there is nothing to do unless we are a
@@ -565,22 +770,32 @@ DBViewWrapper.prototype = {
this.beginViewUpdate();
let msgDatabase = this.displayedFolder.msgDatabase;
if (msgDatabase) {
let dbFolderInfo = msgDatabase.dBFolderInfo;
// - retrieve persisted sort information
this._sort = [[dbFolderInfo.sortType, dbFolderInfo.sortOrder]];
// - retrieve persisted display settings
- this._viewFlags = dbFolderInfo.viewFlags;
+ this.__viewFlags = dbFolderInfo.viewFlags;
+ // Make sure the threaded bit is set if group-by-sort is set. The views
+ // encode 3 states in 2-bits, and we want to avoid that odd-man-out
+ // state.
+ if (this.__viewFlags & nsMsgViewFlagsType.kGroupBySort) {
+ this.__viewFlags |= nsMsgViewFlagsType.kThreadedDisplay;
+ this._ensureValidSort();
+ }
// - retrieve virtual folder configuration
if (aFolder.flags & nsMsgFolderFlags.Virtual) {
let virtFolder = VirtualFolderHelper.wrapVirtualFolder(aFolder);
- this._underlyingFolders = virtFolder.searchFolders;
+ // Filter out the server roots; they only exist for UI reasons.
+ this._underlyingFolders =
+ [folder for each ([, folder] in Iterator(virtFolder.searchFolders))
+ if (!folder.isServer)];
this._underlyingData = (this._underlyingFolders.length > 1) ?
this.kUnderlyingMultipleFolder :
this.kUnderlyingRealFolder;
// figure out if we are using online IMAP searching
this.search.onlineSearch = virtFolder.onlineSearch;
// retrieve and chew the search query
@@ -624,32 +839,87 @@ DBViewWrapper.prototype = {
this.setMailView(MailViewConstants.kViewItemTags,
"$label" + (mailViewIndex-1));
else
this.setMailView(mailViewIndex);
}
}
}
+ if (!this.isVirtual) {
+ this.folderLoading = true;
+ FolderNotificationHelper.updateFolderAndNotifyOnLoad(
+ this.displayedFolder, this, this.listener.msgWindow);
+ }
+
+ // we do this after kicking off the update because this could initiate a
+ // search which could fight our explicit updateFolder call if the search
+ // is already outstanding.
if (this.shouldShowMessagesForFolderImmediately())
this._enterFolder();
+ },
- this.folderLoading = true;
- if (!this.isVirtual)
- FolderNotificationHelper.updateFolderAndNotifyOnLoad(
- this.displayedFolder, this, this.listener.msgWindow);
+ /**
+ * Open a synthetic view provider as backing our view.
+ */
+ openSynthetic: function DBViewWrapper_openSynthetic(aSyntheticView) {
+ this.close();
+
+ this._underlyingData = this.kUnderlyingSynthetic;
+ this._syntheticView = aSyntheticView;
+
+ this.search = new SearchSpec(this);
+ this._sort = this._syntheticView.defaultSort.concat();
+
+ this._applyViewChanges();
+ },
+
+ /**
+ * Makes us irrevocavbly be a search view, for use in search windows.
+ * Once you call this, you are not allowed to use us for anything
+ * but a search view!
+ * We add a 'searchFolders' property that allows you to control what
+ * folders we are searching over.
+ */
+ openSearchView: function DBViewWrapper_openSearchView() {
+ this.close();
+
+ this._underlyingData = this.kUnderlyingSearchView;
+ this._underlyingFolders = [];
+
+ let dis = this;
+ this.__defineGetter__('searchFolders', function() {
+ return dis._underlyingFolders;
+ });
+ this.__defineSetter__('searchFolders', function(aSearchFolders) {
+ dis._underlyingFolders = aSearchFolders;
+ dis._applyViewChanges();
+ });
+
+ this.search = new SearchSpec(this);
+ // the search view uses the order in which messages are added as the
+ // order by default.
+ this._sort = [[nsMsgViewSortType.byNone, nsMsgViewSortOrder.ascending]];
+
+ this._applyViewChanges();
},
get folderLoading() {
return this._folderLoading;
},
set folderLoading(aFolderLoading) {
if (this._folderLoading == aFolderLoading)
return;
this._folderLoading = aFolderLoading;
+ // tell the folder about what is going on so it can remove its db change
+ // listener and restore it, respectively.
+ if (aFolderLoading)
+ this.displayedFolder.startFolderLoading();
+ else
+ this.displayedFolder.endFolderLoading();
this.listener.onFolderLoading(aFolderLoading);
},
get searching() {
return this._searching;
},
set searching(aSearching) {
if (aSearching == this._searching)
@@ -692,17 +962,18 @@ DBViewWrapper.prototype = {
/**
* Creates a view appropriate to the current settings of the folder display
* widget, returning it. The caller is responsible to assign the result to
* this.dbView (or whatever it wants to do with it.)
*/
_createView: function DBViewWrapper__createView() {
let dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=";
- let viewFlags = this._viewFlags;
+ // we will have saved these off when closing our view
+ let viewFlags = this.__viewFlags || 0;
// real folders are subject to the most interest set of possibilities...
if (this._underlyingData == this.kUnderlyingRealFolder) {
// quick-search inherits from threaded which inherits from group, so this
// is right to choose it first.
if (this.search.hasSearchTerms)
dbviewContractId += "quicksearch";
else if (this.showGroupedBySort)
@@ -713,66 +984,80 @@ DBViewWrapper.prototype = {
dbviewContractId += "watchedthreadswithunread";
else
dbviewContractId += "threaded";
}
// if we're dealing with virtual folders, the answer is always an xfvf
else if (this._underlyingData == this.kUnderlyingMultipleFolder) {
dbviewContractId += "xfvf";
}
- else if (this._underlyingData == this.kUnderlyingSynthetic) {
+ else { // kUnderlyingSynthetic or kUnderlyingSearchView
dbviewContractId += "search";
}
+ // and now zero the saved-off flags.
+ this.__viewFlags = null;
+
let dbView = Cc[dbviewContractId]
.createInstance(Ci.nsIMsgDBView);
dbView.init(this.listener.messenger, this.listener.msgWindow,
this.listener.threadPaneCommandUpdater);
// use the least-specific sort so we can clock them back through to build up
// the correct sort order...
- let [sortType, sortOrder] = this._sort[this._sort.length-1];
+ let [sortType, sortOrder, sortCustomCol] =
+ this._getSortDetails(this._sort.length-1);
let outCount = {};
// when the underlying folder is a single real folder (virtual or no), we
// tell the view about the underlying folder.
if (this.isSingleFolder) {
dbView.open(this._underlyingFolders[0], sortType, sortOrder, viewFlags,
outCount);
// but if it's a virtual folder, we need to tell the db view about the
// the display (virtual) folder so it can store all the view-specific
// data there (things like the active mail view and such that go in
// dbFolderInfo.)
if (this.isVirtual)
dbView.viewFolder = this.displayedFolder;
}
// when we're dealing with a multi-folder virtual folder, we just tell the
// db view about the display folder. (It gets its own XFVF view, so it
// knows what to do.)
+ // and for a synthetic folder, displayedFolder is null anyways
else {
dbView.open(this.displayedFolder, sortType, sortOrder, viewFlags,
outCount);
}
+ if (sortCustomCol)
+ dbView.curCustomColumn = sortCustomCol;
+
+ // we all know it's a tree view, make sure the interface is available
+ // so no one else has to do this.
+ dbView.QueryInterface(Ci.nsITreeView);
// clock through the rest of the sorts, if there are any
for (let iSort = this._sort.length - 2; iSort >=0; iSort--) {
- [sortType, sortOrder] = this._sort[iSort];
+ [sortType, sortOrder, sortCustomCol] = this._getSortDetails(iSort);
+ if (sortCustomCol)
+ dbView.curCustomColumn = sortCustomCol;
dbView.sort(sortType, sortOrder);
}
return dbView;
},
/**
* Callback method invoked by FolderNotificationHelper when our folder is
* loaded. Assuming we are still interested in the folder, we enter the
* folder via _enterFolder.
*/
_folderLoaded: function DBViewWrapper__folderLoaded(aFolder) {
if (aFolder == this.displayedFolder) {
this.folderLoading = false;
this._enterFolder();
+ this.listener.onAllMessagesLoaded();
}
},
/**
* Enter this.displayedFolder if we have not yet entered it.
*
* Things we do on entering a folder:
* - clear the folder's biffState!
@@ -780,19 +1065,21 @@ DBViewWrapper.prototype = {
*/
_enterFolder: function DBViewWrapper__enterFolder() {
if (this._enteredFolder)
return;
this.displayedFolder.biffState =
Ci.nsIMsgFolder.nsMsgBiffState_NoMail;
- this.listener.onDisplayingFolder();
+ // we definitely want a view at this point; force the view.
+ this._viewUpdateDepth = 0;
+ this._applyViewChanges();
- this.endViewUpdate();
+ this.listener.onDisplayingFolder();
this._enteredFolder = true;
},
/**
* Renames, moves to the trash, it's all crazy. We have to update all our
* references when this happens.
*/
@@ -842,47 +1129,174 @@ DBViewWrapper.prototype = {
return;
}
// if we are virtual, this will update the search session which draws its
// search scopes from this._underlyingFolders anyways.
this._applyViewChanges();
},
/**
+ * Compacting a local folder nukes its message keys, requiring the view to be
+ * rebuilt. If the folder is IMAP, it doesn't matter because the UIDs are
+ * the message keys and we can ignore it. In the local case we want to
+ * notify our listener so they have a chance to save the selected messages.
+ */
+ _aboutToCompactFolder: function DBViewWrapper__aboutToCompactFolder(aFolder) {
+ // IMAP compaction does not affect us unless we are holding headers
+ if (aFolder.server.type == "imap")
+ return;
+
+ // we will have to re-create the view, so nuke the view now.
+ if (this.dbView) {
+ this.listener.onDestroyingView(true);
+ this.search.dissociateView(this.dbView);
+ this.dbView.close();
+ this.dbView = null;
+ }
+ },
+
+ /**
+ * Compaction is all done, let's re-create the view! (Unless the folder is
+ * IMAP, in which case we are ignoring this event sequence.)
+ */
+ _compactedFolder: function DBViewWrapper__compactedFolder(aFolder) {
+ // IMAP compaction does not affect us unless we are holding headers
+ if (aFolder.server.type == "imap")
+ return;
+
+ this.refresh();
+ },
+
+ /**
+ * DB Views need help to know when their move / deletion operations complete.
+ * This happens in both single-folder and multiple-folder backed searches.
+ * In the latter case, there is potential danger that we tell a view that did
+ * not initiate the move / deletion but has kicked off its own about the
+ * completion and confuse it. However, that's on the view code.
+ */
+ _deleteCompleted: function DBViewWrapper__deleteCompleted(aFolder) {
+ if (this.dbView)
+ this.dbView.onDeleteCompleted(true);
+ this.listener.onMessagesRemoved();
+ },
+
+ /**
+ * See _deleteCompleted for an explanation of what is going on.
+ */
+ _deleteFailed: function DBViewWrapper__deleteFailed(aFolder) {
+ if (this.dbView)
+ this.dbView.onDeleteCompleted(false);
+ this.listener.onMessageRemovalFailed();
+ },
+
+ /**
+ * If the displayed folder had its total message count or total unread message
+ * count change, notify the listener. (Note: only for the display folder;
+ * not the underlying folders!)
+ */
+ _messageCountsChanged: function DBViewWrapper__messageCountsChanged(aFolder) {
+ if (aFolder == this.displayedFolder)
+ this.listener.onMessageCountsChanged();
+ },
+
+ /**
+ * @return the current set of viewFlags. This may be:
+ * - A modified set of flags that are pending application because a view
+ * update is in effect and we don't want to modify the view when it's just
+ * going to get destroyed.
+ * - The live set of flags from the current dbView.
+ * - The 'limbo' set of flags because we currently lack a view but will have
+ * one soon (and then we will apply the flags).
+ */
+ get _viewFlags DBViewWrapper_get__viewFlags() {
+ if (this.__viewFlags != null)
+ return this.__viewFlags;
+ if (this.dbView)
+ return this.dbView.viewFlags;
+ return 0;
+ },
+ /**
+ * Update the view flags to use on the view. If we are in a view update or
+ * currently don't have a view, we save the view flags for later usage when
+ * the view gets (re)built. If we have a view, depending on what's happening
+ * we may re-create the view or just set the bits. The rules/reasons are:
+ * - XFVF views can handle the flag changes, just set the flags.
+ * - Single-folder threaded/unthreaded can handle a change to/from unthreaded/
+ * threaded, so set it.
+ * - Single-folder can _not_ handle a change between grouped and not-grouped,
+ * so re-generate the view.
+ */
+ set _viewFlags DBViewWrapper_set__viewFlags(aViewFlags) {
+ if (this._viewUpdateDepth || !this.dbView)
+ this.__viewFlags = aViewFlags;
+ else {
+ let oldFlags = this.dbView.viewFlags;
+ let changedFlags = oldFlags ^ aViewFlags;
+ if ((this.isVirtual && this.isMultiFolder) ||
+ (this.isSingleFolder &&
+ !(changedFlags & nsMsgViewFlagsType.kGroupBySort))) {
+ this.dbView.viewFlags = aViewFlags;
+ // ugh, and the single folder case needs us to re-apply his sort...
+ if (this.isSingleFolder)
+ this.dbView.sort(this.dbView.sortType, this.dbView.sortOrder);
+ this.listener.onSortChanged();
+ }
+ else {
+ this.__viewFlags = aViewFlags;
+ this._applyViewChanges();
+ }
+ }
+ },
+
+ /**
* Apply accumulated changes to the view. If we are in a batch, we do
* nothing, relying on endDisplayUpdate to call us.
*/
_applyViewChanges: function DBViewWrapper__applyViewChanges() {
// if we are in a batch, wait for endDisplayUpdate to be called to get us
// out to zero.
if (this._viewUpdateDepth)
return;
// make the dbView stop being a search listener if it is one
if (this.dbView) {
+ // save the view's flags if it has any and we haven't already overridden
+ // them.
+ if (this.__viewFlags == null)
+ this.__viewFlags = this.dbView.viewFlags;
+ this.listener.onDestroyingView(true); // we will re-create it!
this.search.dissociateView(this.dbView);
this.dbView.close();
+ this.dbView = null;
}
this.dbView = this._createView();
+ // if the synthetic view defines columns, add those for it
+ if (this.isSynthetic) {
+ for (let [, customCol] in Iterator(this._syntheticView.customColumns)) {
+ customCol.bindToView(this.dbView);
+ this.dbView.addColumnHandler(customCol.id, customCol);
+ }
+ }
this.listener.onCreatedView();
// this ends up being a no-op if there are no search terms
this.search.associateView(this.dbView);
// If we are searching, then the search will generate the all messages
// loaded notification. Although in some cases the search may have
// completed by now, that is not a guarantee. The search logic is
// time-slicing, which is why this can vary. (If it uses up its time
// slices, it will re-schedule itself, returning to us before completing.)
// Which is why we always defer to the search if one is active.
- if (!this.searching)
+ // If we are loading the folder, the load completion will also notify us,
+ // so we should not generate all messages loaded right now.
+ if (!this.searching && !this.folderLoading)
this.listener.onAllMessagesLoaded();
},
-
/**
* Check if the folder or (optionally) one of its ancestors has any one of the
* provided flags set.
*
* In the event there is a folder with both the SentMail and Inbox flags set,
* it will be treated as an Inbox, and not as a SentMail folder.
*
* @param {nsIMsgFolder} aMsgFolder The folder whose flags you want to check.
@@ -912,153 +1326,355 @@ DBViewWrapper.prototype = {
// and not a SENT folder
const nsMsgFolderFlags = Ci.nsMsgFolderFlags;
return !((aFlags & nsMsgFolderFlags.SentMail) &&
(aMsgFolder.flags & nsMsgFolderFlags.Inbox));
}
},
+ get isMailFolder() {
+ return this.displayedFolder &&
+ (this.displayedFolder.flags & nsMsgFolderFlags.Mail);
+ },
+
+ get isNewsFolder() {
+ return this.displayedFolder &&
+ (this.displayedFolder.flags & nsMsgFolderFlags.Newsgroup);
+ },
+
OUTGOING_FOLDER_FLAGS: nsMsgFolderFlags.SentMail |
nsMsgFolderFlags.Drafts |
nsMsgFolderFlags.Queue |
- nsMsgFolderFlags.Template,
+ nsMsgFolderFlags.Templates,
/**
* @return true if the folder is not known to be a special outgoing folder
* or the descendent of a special outgoing folder.
*/
get isIncomingFolder() {
return !this._isSpecialFolder(this.displayedFolder,
- this.OUTGOING_FOLDER_FLAGS,
- true);
+ this.OUTGOING_FOLDER_FLAGS,
+ true);
},
/**
* @return true if the folder is an outgoing folder by virtue of being a
* sent mail folder, drafts folder, queue folder, or template folder,
* or being a sub-folder of one of those types of folders.
*/
get isOutgoingFolder() {
- return !this._isSpecialFolder(this.displayedFolder,
- this.OUTGOING_FOLDER_FLAGS,
- true);
+ return this._isSpecialFolder(this.displayedFolder,
+ this.OUTGOING_FOLDER_FLAGS,
+ true);
},
get isVirtual() {
return Boolean(this.displayedFolder &&
(this.displayedFolder.flags & nsMsgFolderFlags.Virtual));
},
+ /**
+ * Prevent view updates from running until a paired |endViewUpdate| call is
+ * made. This is an advisory method intended to aid us in performing
+ * redundant view re-computations and does not forbid us from building the
+ * view earlier if we have a good reason.
+ * Since calling endViewUpdate will compel a view update when the update
+ * depth reaches 0, you should only call this method if you are sure that
+ * you will need the view to be re-built. If you are doing things like
+ * changing to/from threaded mode that do not cause the view to be rebuilt,
+ * you should just set those attributes directly.
+ */
beginViewUpdate: function DBViewWrapper_beginViewUpdate() {
this._viewUpdateDepth++;
},
- endViewUpdate: function DBViewWrapper_endViewUpdate() {
- if (--this._viewUpdateDepth == 0) {
+ /**
+ * Conclude a paired call to |endViewUpdate|. Assuming the view depth has
+ * reached 0 with this call, the view will be re-created with the current
+ * settings.
+ */
+ endViewUpdate: function DBViewWrapper_endViewUpdate(aForceLevel) {
+ if (--this._viewUpdateDepth == 0)
this._applyViewChanges();
- }
+ // Avoid pathological situations.
+ if (this._viewUpdateDepth < 0)
+ this._viewUpdateDepth = 0;
+ },
+
+ /**
+ * @return the primary sort type (as one of the numeric constants from
+ * nsMsgViewSortType).
+ */
+ get primarySortType() {
+ return this._sort[0][0];
+ },
+
+ /**
+ * @return the primary sort order (as one of the numeric constants from
+ * nsMsgViewSortOrder.)
+ */
+ get primarySortOrder() {
+ return this._sort[0][1];
+ },
+
+ /**
+ * @return true if the dominant sort is ascending.
+ */
+ get isSortedAscending() {
+ return this._sort.length &&
+ this._sort[0][1] == nsMsgViewSortOrder.ascending;
+ },
+ /**
+ * @return true if the dominant sort is descending.
+ */
+ get isSortedDescending() {
+ return this._sort.length &&
+ this._sort[0][1] == nsMsgViewSortOrder.descending;
+ },
+ /**
+ * Indicate if we are sorting by time or something correlated with time.
+ *
+ * @return true if the dominant sort is by time.
+ */
+ get sortImpliesTemporalOrdering() {
+ if (!this._sort.length)
+ return false;
+ let sortType = this._sort[0][0];
+ return sortType == nsMsgViewSortType.byDate ||
+ sortType == nsMsgViewSortType.byReceived ||
+ sortType == nsMsgViewSortType.byId ||
+ sortType == nsMsgViewSortType.byThread;
+ },
+
+ sortAscending: function() {
+ if (!this.isSortedAscending)
+ this.magicSort(this._sort[0][0], nsMsgViewSortOrder.ascending);
+ },
+ sortDescending: function() {
+ if (!this.isSortedDescending)
+ this.magicSort(this._sort[0][0], nsMsgViewSortOrder.descending);
},
/**
* Explicit sort command. We ignore all previous sort state and only apply
* what you tell us. If you want implied secondary sort, use |magicSort|.
* You must use this sort command, and never directly call the sort commands
* on the underlying db view! If you do not, make sure to fight us every
* step of the way, because we will keep clobbering your manually applied
* sort.
*/
sort: function DBViewWrapper_sort(aSortType, aSortOrder,
aSecondaryType, aSecondaryOrder) {
this._sort = [[aSortType, aSortOrder]];
if (aSecondaryType != null && aSecondaryOrder != null)
this._sort.push([aSecondaryType, aSecondaryOrder]);
+ // make sure the sort won't make the view angry...
+ this._ensureValidSort();
// if we are not in a view update, invoke the sort.
if ((this._viewUpdateDepth == 0) && this.dbView) {
for (let iSort = this._sort.length - 1; iSort >=0; iSort--) {
// apply them in the reverse order
let [sortType, sortOrder] = this._sort[iSort];
this.dbView.sort(sortType, sortOrder);
}
+ // (only generate the event since we're not in a update batch)
+ this.listener.onSortChanged();
}
// (if we are in a view update, then a new view will be created when the
// update ends, and it will just use the new sort order anyways.)
},
/**
+ * Logic that compensates for custom column identifiers being provided as
+ * sort types.
+ *
+ * @return [sort type, sort order, sort custom column name]
+ */
+ _getSortDetails: function(aIndex) {
+ let [sortType, sortOrder] = this._sort[aIndex];
+ let sortCustomColumn = null;
+ let sortTypeType = typeof(sortType);
+ if (sortTypeType != "number") {
+ sortCustomColumn = (sortTypeType == "string") ? sortType : sortType.id;
+ sortType = nsMsgViewSortType.byCustom;
+ }
+
+ return [sortType, sortOrder, sortCustomColumn];
+ },
+
+ /**
* Accumulates implied secondary sorts based on multiple calls to this method.
* This is intended to be hooked up to be controlled by the UI.
* Because we are lazy, we actually just poke the view's sort method and save
* the apparent secondary sort. This also allows perfect compliance with the
* way this used to be implemented!
*/
magicSort: function DBViewWrapper_magicSort(aSortType, aSortOrder) {
if (this.dbView) {
- this.dbView.sort(aSortType, aSortOrder);
// so, the thing we just set obviously will be there
this._sort = [[aSortType, aSortOrder]];
+ // (make sure it is valid...)
+ this._ensureValidSort();
+ // apply the sort to see what happens secondary-wise
+ this.dbView.sort(aSortType, aSortOrder);
// there is only a secondary sort if it's not none and not the same.
if (this.dbView.secondarySortType != nsMsgViewSortType.byNone &&
this.dbView.secondarySortType != aSortType)
this._sort.push([this.dbView.secondarySortType,
this.dbView.secondarySortOrder]);
+ // only tell our listener if we're not in a view update batch
+ if (this._viewUpdateDepth == 0)
+ this.listener.onSortChanged();
}
},
+ /**
+ * Make sure the current sort is valid under our other constraints, make it
+ * safe if it is not. Most specifically, some sorts are illegal when
+ * grouping by sort, and we reset the sort to date in those cases.
+ *
+ * @param aViewFlags Optional set of view flags to consider instead of the
+ * potentially live view flags.
+ */
+ _ensureValidSort: function DBViewWrapper_ensureValidSort(aViewFlags) {
+ if ((aViewFlags != null ? aViewFlags : this._viewFlags) &
+ nsMsgViewFlagsType.kGroupBySort) {
+ // We cannot be sorting by thread, id, none, or size. If we are, switch
+ // to sorting by date.
+ let sortType = this._sort[0][0];
+ if (sortType == nsMsgViewSortType.byThread ||
+ sortType == nsMsgViewSortType.byId ||
+ sortType == nsMsgViewSortType.byNone ||
+ sortType == nsMsgViewSortType.bySize)
+ this._sort = [nsMsgViewSortType.byDate, this._sort[0][1]];
+ }
+ },
+
+ /**
+ * @return true if we are grouped-by-sort, false if not. If we are not
+ * grouped by sort, then we are either threaded or unthreaded; check
+ * the showThreaded property to find out which of those it is.
+ */
get showGroupedBySort() {
return Boolean(this._viewFlags & nsMsgViewFlagsType.kGroupBySort);
},
+ /**
+ * Enable grouped-by-sort which is mutually exclusive with threaded display
+ * (as controlled/exposed by showThreaded). Grouped-by-sort is not legal
+ * for sorts by thread/id/size/none and enabling this will cause us to change
+ * our sort to by date in those cases.
+ */
set showGroupedBySort(aShowGroupBySort) {
if (this.showGroupedBySort != aShowGroupBySort) {
- if (aShowGroupBySort)
- this._viewFlags |= nsMsgViewFlagsType.kGroupBySort;
+ if (aShowGroupBySort) {
+ // do not apply the flag change until we have made the sort safe
+ let viewFlags = this._viewFlags |
+ nsMsgViewFlagsType.kGroupBySort |
+ nsMsgViewFlagsType.kThreadedDisplay;
+ this._ensureValidSort(viewFlags);
+ this._viewFlags = viewFlags;
+ }
+ // maybe we shouldn't do anything in this case?
else
- this._viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
- // lose the threading bit...
- this._viewFlags &= ~nsMsgViewFlagsType.kThreadedDisplay;
- this._applyViewChanges();
+ this._viewFlags &= ~(nsMsgViewFlagsType.kGroupBySort |
+ nsMsgViewFlagsType.kThreadedDisplay);
}
},
+ /**
+ * Are we showing ignored/killed threads? Only meaningful for newsgroups.
+ */
get showIgnored() {
return Boolean(this._viewFlags & nsMsgViewFlagsType.kShowIgnored);
},
+ /**
+ * Set whether we are showing ignored/killed threads. Only meaningful for
+ * newsgroups.
+ */
set showIgnored(aShowIgnored) {
if (this.showIgnored != aShowIgnored) {
if (aShowIgnored)
this._viewFlags |= nsMsgViewFlagsType.kShowIgnored;
else
this._viewFlags &= ~nsMsgViewFlagsType.kShowIgnored;
- this._applyViewChanges();
+ }
+ },
+
+ /**
+ * @return true if we are in threaded display (as opposed to grouped or
+ * unthreaded.)
+ */
+ get showThreaded() {
+ return (this._viewFlags & nsMsgViewFlagsType.kThreadedDisplay) &&
+ !(this._viewFlags & nsMsgViewFlagsType.kGroupBySort);
+ },
+ /**
+ * Set us to threaded display mode when set to true. If we are already in
+ * threaded display mode, we do nothing. If you want to set us to unthreaded
+ * mode, set |showUnthreaded| to true. (Because we have three modes of
+ * operation: unthreaded, threaded, and grouped-by-sort, we are a tri-state
+ * and setting us to false is ambiguous. We should probably be using a
+ * single attribute with three constants...)
+ */
+ set showThreaded(aShowThreaded) {
+ if (this.showThreaded != aShowThreaded) {
+ let viewFlags = this._viewFlags;
+ if (aShowThreaded)
+ viewFlags |= nsMsgViewFlagsType.kThreadedDisplay;
+ // maybe we shouldn't do anything in this case?
+ else
+ viewFlags &= ~nsMsgViewFlagsType.kThreadedDisplay;
+ // lose the group bit...
+ viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
+ this._viewFlags = viewFlags;
}
},
- get showThreaded() {
- return Boolean(this._viewFlags & nsMsgViewFlagsType.kThreadedDisplay);
+ /**
+ * @return true if we are in unthreaded mode (which means not threaded and
+ * not grouped by sort).
+ */
+ get showUnthreaded() {
+ return Boolean(!(this._viewFlags & (nsMsgViewFlagsType.kGroupBySort |
+ nsMsgViewFlagsType.kThreadedDisplay)));
},
- set showThreaded(aShowThreaded) {
- if (this.showThreaded != aShowThreaded) {
- if (aShowThreaded)
- this._viewFlags |= nsMsgViewFlagsType.kThreadedDisplay;
+ /**
+ * Set to true to put us in unthreaded mode (which means not threaded and
+ * not grouped by sort).
+ */
+ set showUnthreaded(aShowUnthreaded) {
+ if (this.showUnthreaded != aShowUnthreaded) {
+ if (aShowUnthreaded)
+ this._viewFlags &= ~(nsMsgViewFlagsType.kGroupBySort |
+ nsMsgViewFlagsType.kThreadedDisplay);
+ // maybe we shouldn't do anything in this case?
else
- this._viewFlags &= ~nsMsgViewFlagsType.kThreadedDisplay;
- // lose the group bit...
- this._viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
- this._applyViewChanges();
+ this._viewFlags = (this._viewFlags & ~nsMsgViewFlagsType.kGroupBySort) |
+ nsMsgViewFlagsType.kThreadedDisplay;
}
},
+ /**
+ * @return true if we are showing only unread messages.
+ */
get showUnreadOnly() {
return Boolean(this._viewFlags & nsMsgViewFlagsType.kUnreadOnly);
},
+ /**
+ * Enable/disable showing only unread messages using the view's flag-based
+ * mechanism. This functionality can also be approximated using a mail
+ * view (or other search) for unread messages. There also exist special
+ * views for showing messages with unread threads which is different and
+ * has serious limitations because of its nature.
+ */
set showUnreadOnly(aShowUnreadOnly) {
if (this.showUnreadOnly != aShowUnreadOnly) {
if (aShowUnreadOnly)
this._viewFlags |= nsMsgViewFlagsType.kUnreadOnly;
else
this._viewFlags &= ~nsMsgViewFlagsType.kUnreadOnly;
- this._applyViewChanges();
}
},
/**
* Read-only attribute indicating if a 'special view' is in use. There are
* two special views in existence, both of which are concerned about
* showing you threads that have any unread messages in them. They are views
* rather than search predicates because the search mechanism is not capable
@@ -1143,17 +1759,18 @@ DBViewWrapper.prototype = {
*/
setMailView: function DBViewWrapper_setMailView(aMailViewIndex, aData,
aDoNotPersist) {
let mailViewDef = MailViewManager.getMailViewByIndex(aMailViewIndex);
this._mailViewIndex = aMailViewIndex;
this._mailViewData = aData;
- // - update the search terms (this triggers the view update)
+ // - update the search terms
+ // (this triggers a view update if we are not in a batch)
this.search.viewTerms = mailViewDef.makeTerms(this.search.session,
aData);
// - persist the view to the folder.
if (!aDoNotPersist) {
let msgDatabase = this.displayedFolder.msgDatabase;
if (msgDatabase) {
let dbFolderInfo = msgDatabase.dBFolderInfo;
@@ -1165,20 +1782,31 @@ DBViewWrapper.prototype = {
// when we persist it. It's not like the property is really generic
// anyways.
dbFolderInfo.setCharProperty(
MailViewConstants.kViewCurrentTag,
this._mailViewData ? (":" + this._mailViewData) : "");
}
}
- // we don't need to notify the view picker to update because the makeActive that
- // cascades out of the view update will do it for us.
+ // we don't need to notify the view picker to update because the makeActive
+ // that cascades out of the view update will do it for us.
},
+ /**
+ * @return true if the row at the given index contains a collapsed thread,
+ * false if the row is a collapsed group or anything else.
+ */
+ isCollapsedThreadAtIndex:
+ function DBViewWrapper_isCollapsedThreadAtIndex(aViewIndex) {
+ let flags = this.dbView.getFlagsAt(aViewIndex);
+ return (flags & nsMsgMessageFlags.Elided) &&
+ !(flags & MSG_VIEW_FLAG_DUMMY) &&
+ this.dbView.isContainer(aViewIndex);
+ },
/**
* Perform application-level behaviors related to leaving a folder that have
* nothing to do with our abstraction.
*
* Things we do on leaving a folder:
* - Mark the folder's messages as no longer new
* - Mark all messages read in the folder _if so configured_.
@@ -1197,9 +1825,39 @@ DBViewWrapper.prototype = {
// goDoCommand('cmd_markAllRead').
if (this.dbView &&
this.listener.shouldDeferMessageDisplayUntilAfterServerConnect(
this.displayedFolder.server.type))
this.dbView.doCommand(nsMsgViewCommandType.markAllRead);
}
catch(e){/* ignore */}
},
+
+ /**
+ * Convenience function to retrieve the first nsIMsgDBHdr in any of the
+ * folders backing this view with the given message-id header. This
+ * is for the benefit of FolderDisplayWidget's selection logic.
+ * When thinking about using this, please keep in mind that, currently, this
+ * is O(n) for the total number of messages across all the backing folders.
+ * Since the folder database should already be in memory, this should
+ * ideally not involve any disk I/O.
+ * Additionally, duplicate message-ids can and will happen, but since we
+ * are using the message database's getMsgHdrForMessageID method to be fast,
+ * our semantics are limited to telling you about only the first one we find.
+ *
+ * @param aMessageId The message-id of the message you want.
+ * @return The first nsIMsgDBHdr found in any of the underlying folders with
+ * the given message header, null if none are found. The fact that we
+ * return something does not guarantee that it is actually visible in the
+ * view. (The search may be filtering it out.)
+ */
+ getMsgHdrForMessageID: function DBViewWrapper_getMsgHdrForMessageID(
+ aMessageId) {
+ if (!this._underlyingFolders)
+ return null;
+ for (let [, folder] in Iterator(this._underlyingFolders)) {
+ let msgHdr = folder.msgDatabase.getMsgHdrForMessageID(aMessageId);
+ if (msgHdr)
+ return msgHdr;
+ }
+ return null;
+ },
};
\ No newline at end of file
--- a/mailnews/base/src/nsMessenger.cpp
+++ b/mailnews/base/src/nsMessenger.cpp
@@ -253,23 +253,23 @@ public:
} m_outputFormat;
nsString m_msgBuffer;
nsCString m_contentType; // used only when saving attachment
nsCOMPtr<nsITransfer> mTransfer;
nsCOMPtr<nsIUrlListener> mListener;
nsCOMPtr<nsIURI> mListenerUri;
- PRInt32 mProgress;
- PRInt32 mContentLength;
+ PRInt64 mProgress;
+ PRInt64 mMaxProgress;
PRBool mCanceled;
PRBool mInitialized;
PRBool mUrlHasStopped;
PRBool mRequestHasStopped;
- nsresult InitializeDownload(nsIRequest * aRequest, PRInt32 aBytesDownloaded);
+ nsresult InitializeDownload(nsIRequest * aRequest, PRUint32 aBytesDownloaded);
};
class nsSaveAllAttachmentsState
{
public:
nsSaveAllAttachmentsState(PRUint32 count,
const char **contentTypeArray,
const char **urlArray,
@@ -1432,17 +1432,17 @@ nsSaveMsgListener::nsSaveMsgListener(nsI
mListener = aListener;
mUrlHasStopped = PR_FALSE;
mRequestHasStopped = PR_FALSE;
// rhp: for charset handling
m_doCharsetConversion = PR_FALSE;
m_saveAllAttachmentsState = nsnull;
mProgress = 0;
- mContentLength = -1;
+ mMaxProgress = -1;
mCanceled = PR_FALSE;
m_outputFormat = eUnknown;
mInitialized = PR_FALSE;
m_dataBuffer = new char[FOUR_K];
}
nsSaveMsgListener::~nsSaveMsgListener()
{
@@ -1559,44 +1559,51 @@ nsSaveMsgListener::OnStopCopy(nsresult a
if (m_file)
m_file->Remove(PR_FALSE);
Release(); // all done kill ourself
return aStatus;
}
// initializes the progress window if we are going to show one
// and for OSX, sets creator flags on the output file
-nsresult nsSaveMsgListener::InitializeDownload(nsIRequest * aRequest, PRInt32 aBytesDownloaded)
+nsresult nsSaveMsgListener::InitializeDownload(nsIRequest * aRequest, PRUint32 aBytesDownloaded)
{
nsresult rv = NS_OK;
mInitialized = PR_TRUE;
nsCOMPtr<nsIChannel> channel (do_QueryInterface(aRequest));
if (!channel)
return rv;
- // Set content length if we haven't already got it.
- if (mContentLength == -1)
- channel->GetContentLength(&mContentLength);
+ // Get the max progress from the URL if we haven't already got it.
+ if (mMaxProgress == -1)
+ {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(uri));
+ if (mailnewsUrl)
+ mailnewsUrl->GetMaxProgress(&mMaxProgress);
+ }
if (!m_contentType.IsEmpty())
{
nsCOMPtr<nsIMIMEService> mimeService (do_GetService(NS_MIMESERVICE_CONTRACTID));
nsCOMPtr<nsIMIMEInfo> mimeinfo;
mimeService->GetFromTypeAndExtension(m_contentType, EmptyCString(), getter_AddRefs(mimeinfo));
nsCOMPtr<nsILocalFile> outputFile = do_QueryInterface(m_file);
- // create a download progress window
- // XXX: we don't want to show the progress dialog if the download is really small.
- // but what is a small download? Well that's kind of arbitrary
- // so make an arbitrary decision based on the content length of the attachment
- if (mContentLength != -1 && mContentLength > aBytesDownloaded * 2)
+ // create a download progress window
+ // We don't want to show the progress dialog if the download is really small.
+ // but what is a small download? Well that's kind of arbitrary
+ // so make an arbitrary decision based on the content length of the
+ // attachment -- show it if less than half of the download has completed
+ if (mMaxProgress != -1 && mMaxProgress > aBytesDownloaded * 2)
{
nsCOMPtr<nsITransfer> tr = do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
if (tr && outputFile)
{
PRTime timeDownloadStarted = PR_Now();
nsCOMPtr<nsIURI> outputURI;
NS_NewFileURI(getter_AddRefs(outputURI), outputFile);
@@ -1619,33 +1626,16 @@ nsresult nsSaveMsgListener::InitializeDo
nsCOMPtr<nsIAppleFileDecoder> appleFileDecoder = do_CreateInstance(NS_IAPPLEFILEDECODER_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv) && appleFileDecoder)
{
rv = appleFileDecoder->Initialize(m_outputStream, outputFile);
if (NS_SUCCEEDED(rv))
m_outputStream = do_QueryInterface(appleFileDecoder, &rv);
}
}
- else
- {
- if (mimeinfo)
- {
- PRUint32 aMacType;
- PRUint32 aMacCreator;
- if (NS_SUCCEEDED(mimeinfo->GetMacType(&aMacType)) && NS_SUCCEEDED(mimeinfo->GetMacCreator(&aMacCreator)))
- {
- nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(outputFile, &rv);
- if (NS_SUCCEEDED(rv) && macFile)
- {
- macFile->SetFileCreator((OSType)aMacCreator);
- macFile->SetFileType((OSType)aMacType);
- }
- }
- }
- }
#endif // XP_MACOSX
}
return rv;
}
NS_IMETHODIMP
nsSaveMsgListener::OnStartRequest(nsIRequest* request, nsISupports* aSupport)
{
@@ -1753,17 +1743,17 @@ nsSaveMsgListener::OnStopRequest(nsIRequ
delete m_saveAllAttachmentsState;
m_saveAllAttachmentsState = nsnull;
}
}
if(mTransfer)
{
- mTransfer->OnProgressChange(nsnull, nsnull, mContentLength, mContentLength, mContentLength, mContentLength);
+ mTransfer->OnProgressChange64(nsnull, nsnull, mMaxProgress, mMaxProgress, mMaxProgress, mMaxProgress);
mTransfer->OnStateChange(nsnull, nsnull, nsIWebProgressListener::STATE_STOP |
nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
mTransfer = nsnull; // break any circular dependencies between the progress dialog and use
}
if (mUrlHasStopped && mListener)
mListener->OnStopRunningUrl(mListenerUri, rv);
@@ -1811,17 +1801,17 @@ nsSaveMsgListener::OnDataAvailable(nsIRe
else
rv = m_outputStream->Write(m_dataBuffer, readCount, &writeCount);
available -= readCount;
}
}
if (NS_SUCCEEDED(rv) && mTransfer) // Send progress notification.
- mTransfer->OnProgressChange(nsnull, request, mProgress, mContentLength, mProgress, mContentLength);
+ mTransfer->OnProgressChange64(nsnull, request, mProgress, mMaxProgress, mProgress, mMaxProgress);
}
return rv;
}
#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties"
nsresult
nsMessenger::InitStringBundle()
@@ -2416,35 +2406,35 @@ nsDelAttachListener::OnStopRequest(nsIRe
mMessageFolder->CopyDataDone();
if (NS_FAILED(aStatusCode))
return aStatusCode;
// called when we complete processing of the StreamMessage request.
// This is called before OnStopRunningUrl().
nsresult rv;
- // copy the file back into the folder. Note: if we set msgToReplace then
- // CopyFileMessage() fails, do the delete ourselves
+ // copy the file back into the folder. Note: setting msgToReplace only copies
+ // metadata, so we do the delete ourselves
nsCOMPtr<nsIMsgCopyServiceListener> listenerCopyService;
rv = this->QueryInterface( NS_GET_IID(nsIMsgCopyServiceListener), getter_AddRefs(listenerCopyService) );
NS_ENSURE_SUCCESS(rv,rv);
mMsgFileStream->Close();
mMsgFileStream = nsnull;
mNewMessageKey = PR_UINT32_MAX;
nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID);
m_state = eCopyingNewMsg;
// clone file because nsIFile on Windows caches the wrong file size.
nsCOMPtr <nsIFile> clone;
mMsgFile->Clone(getter_AddRefs(clone));
if (copyService)
{
nsCString originalKeys;
mOriginalMessage->GetStringProperty("keywords", getter_Copies(originalKeys));
- rv = copyService->CopyFileMessage(clone, mMessageFolder, nsnull, PR_FALSE,
+ rv = copyService->CopyFileMessage(clone, mMessageFolder, mOriginalMessage, PR_FALSE,
mOrigMsgFlags, originalKeys, listenerCopyService, mMsgWindow);
}
return rv;
}
//
// nsIStreamListener
//
--- a/mailnews/base/src/nsMsgAccountManager.cpp
+++ b/mailnews/base/src/nsMsgAccountManager.cpp
@@ -2719,16 +2719,23 @@ NS_IMETHODIMP VirtualFolderChangeListene
NS_IMETHODIMP VirtualFolderChangeListener::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
{
nsCOMPtr <nsIMsgDatabase> msgDB = do_QueryInterface(instigator);
if (msgDB)
msgDB->RemoveListener(this);
return NS_OK;
}
+NS_IMETHODIMP
+VirtualFolderChangeListener::OnEvent(nsIMsgDatabase *aDB, const char *aEvent)
+{
+ return NS_OK;
+}
+
+
NS_IMETHODIMP VirtualFolderChangeListener::OnReadChanged(nsIDBChangeListener *aInstigator)
{
return NS_OK;
}
NS_IMETHODIMP VirtualFolderChangeListener::OnJunkScoreChanged(nsIDBChangeListener *aInstigator)
{
return NS_OK;
@@ -3091,18 +3098,22 @@ NS_IMETHODIMP nsMsgAccountManager::GetAl
rootFolder->ListDescendents(allDescendents);
}
}
PRUint32 folderCount;
rv = allDescendents->Count(&folderCount);
NS_ENSURE_SUCCESS(rv, rv);
// Create an nsIMutableArray from the nsISupportsArray
+ nsCOMPtr<nsIMsgFolder> folder;
for (i = 0; i < folderCount; i++)
- folderArray->AppendElement(allDescendents->ElementAt(i), PR_FALSE);
+ {
+ folder = do_QueryElementAt(allDescendents, i);
+ folderArray->AppendElement(folder, PR_FALSE);
+ }
NS_ADDREF(*aAllFolders = folderArray);
return rv;
}
NS_IMETHODIMP nsMsgAccountManager::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item)
{
nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(item);
// just kick out with a success code if the item in question is not a folder
--- a/mailnews/base/src/nsMsgCopyService.cpp
+++ b/mailnews/base/src/nsMsgCopyService.cpp
@@ -600,17 +600,20 @@ nsMsgCopyService::CopyFileMessage(nsIFil
if (NS_FAILED(rv)) goto done;
rv = copyRequest->Init(nsCopyFileMessageType, fileSupport, dstFolder,
isDraft, aMsgFlags, aNewMsgKeywords, listener, window, PR_FALSE);
if (NS_FAILED(rv)) goto done;
if (msgToReplace)
{
- copySource = copyRequest->AddNewCopySource(dstFolder);
+ // The actual source of the message is a file not a folder, but
+ // we still need an nsCopySource to reference the old message header
+ // which will be used to recover message metadata.
+ copySource = copyRequest->AddNewCopySource(nsnull);
if (!copySource)
{
rv = NS_ERROR_OUT_OF_MEMORY;
goto done;
}
copySource->AddMessage(msgToReplace);
}
--- a/mailnews/base/src/nsMsgDBView.cpp
+++ b/mailnews/base/src/nsMsgDBView.cpp
@@ -70,16 +70,17 @@
#include "nsIMsgAccountManager.h"
#include "nsITreeColumns.h"
#include "nsTextFormatter.h"
#include "nsIMutableArray.h"
#include "nsIMimeConverter.h"
#include "nsMsgMessageFlags.h"
#include "nsIPrompt.h"
#include "nsIWindowWatcher.h"
+#include "nsMsgDBCID.h"
nsrefcnt nsMsgDBView::gInstanceCount = 0;
#ifdef SUPPORT_PRIORITY_COLORS
nsIAtom * nsMsgDBView::kHighestPriorityAtom = nsnull;
nsIAtom * nsMsgDBView::kHighPriorityAtom = nsnull;
nsIAtom * nsMsgDBView::kLowestPriorityAtom = nsnull;
nsIAtom * nsMsgDBView::kLowPriorityAtom = nsnull;
@@ -2017,18 +2018,20 @@ NS_IMETHODIMP nsMsgDBView::Open(nsIMsgFo
(void) accountManager->GetUserNeedsToAuthenticate(&userNeedsToAuthenticate);
if (userNeedsToAuthenticate)
return NS_MSG_USER_NOT_AUTHENTICATED;
if (folder) // search view will have a null folder
{
nsCOMPtr <nsIDBFolderInfo> folderInfo;
rv = folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(m_db));
- NS_ENSURE_SUCCESS(rv,rv);
- m_db->AddListener(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgDBService->RegisterPendingListener(folder, this);
m_folder = folder;
m_viewFolder = folder;
SetMRUTimeForFolder(m_folder);
// restore m_sortColumns from db
nsString sortColumnsString;
folderInfo->GetProperty("sortColumns", sortColumnsString);
@@ -2085,16 +2088,23 @@ NS_IMETHODIMP nsMsgDBView::Close()
mTree->RowCountChanged(0, -oldSize);
ClearHdrCache();
if (m_db)
{
m_db->RemoveListener(this);
m_db = nsnull;
}
+ if (m_folder)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgDBService->UnregisterPendingListener(this);
+ }
return NS_OK;
}
NS_IMETHODIMP nsMsgDBView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType,
nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags,
PRInt32 *aCount)
{
NS_ASSERTION(PR_FALSE, "not implemented");
@@ -2177,16 +2187,31 @@ NS_IMETHODIMP nsMsgDBView::GetIndicesFor
PRUint32 datalen = numIndices * sizeof(nsMsgViewIndex);
*indices = (nsMsgViewIndex *)NS_Alloc(datalen);
if (!*indices) return NS_ERROR_OUT_OF_MEMORY;
memcpy(*indices, selection.Elements(), datalen);
return NS_OK;
}
+NS_IMETHODIMP nsMsgDBView::GetMsgHdrsForSelection(nsIMutableArray **aResult)
+{
+ nsMsgViewIndexArray selection;
+ GetSelectedIndices(selection);
+ PRUint32 numIndices = selection.Length();
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetHeadersFromSelection(selection.Elements(), numIndices, messages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ messages.forget(aResult);
+ return rv;
+}
+
NS_IMETHODIMP nsMsgDBView::GetURIsForSelection(PRUint32 *length, char ***uris)
{
nsresult rv = NS_OK;
NS_ENSURE_ARG_POINTER(length);
*length = 0;
NS_ENSURE_ARG_POINTER(uris);
@@ -5591,16 +5616,24 @@ NS_IMETHODIMP nsMsgDBView::OnAnnouncerGo
// tell the tree all the rows have gone away
if (mTree)
mTree->RowCountChanged(0, -saveSize);
return NS_OK;
}
+NS_IMETHODIMP
+nsMsgDBView::OnEvent(nsIMsgDatabase *aDB, const char *aEvent)
+{
+ if (!strcmp(aEvent, "DBOpened"))
+ m_db = aDB;
+ return NS_OK;
+}
+
NS_IMETHODIMP nsMsgDBView::OnReadChanged(nsIDBChangeListener *aInstigator)
{
return NS_OK;
}
NS_IMETHODIMP nsMsgDBView::OnJunkScoreChanged(nsIDBChangeListener *aInstigator)
{
@@ -6890,31 +6923,44 @@ nsresult nsMsgDBView::GetImapDeleteModel
}
//
// CanDrop
//
// Can't drop on the thread pane.
//
+#ifdef MOZILLA_1_9_1_BRANCH
NS_IMETHODIMP nsMsgDBView::CanDrop(PRInt32 index, PRInt32 orient, PRBool *_retval)
+#else
+NS_IMETHODIMP nsMsgDBView::CanDrop(PRInt32 index,
+ PRInt32 orient,
+ nsIDOMDataTransfer *dataTransfer,
+ PRBool *_retval)
+#endif
{
NS_ENSURE_ARG_POINTER(_retval);
*_retval = PR_FALSE;
return NS_OK;
}
//
// Drop
//
// Can't drop on the thread pane.
//
+#ifdef MOZILLA_1_9_1_BRANCH
NS_IMETHODIMP nsMsgDBView::Drop(PRInt32 row, PRInt32 orient)
+#else
+NS_IMETHODIMP nsMsgDBView::Drop(PRInt32 row,
+ PRInt32 orient,
+ nsIDOMDataTransfer *dataTransfer)
+#endif
{
return NS_OK;
}
//
// IsSorted
//
--- a/mailnews/base/src/nsMsgMailSession.cpp
+++ b/mailnews/base/src/nsMsgMailSession.cpp
@@ -522,17 +522,18 @@ nsMsgMailSession::GetDataFilesDir(const
return rv;
}
/********************************************************************************/
NS_IMPL_ISUPPORTS3(nsMsgShutdownService, nsIMsgShutdownService, nsIUrlListener, nsIObserver)
nsMsgShutdownService::nsMsgShutdownService()
-: mProcessedShutdown(PR_FALSE),
+: mQuitMode(nsIAppStartup::eAttemptQuit),
+ mProcessedShutdown(PR_FALSE),
mQuitForced(PR_FALSE),
mReadyToQuit(PR_FALSE)
{
nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
if (observerService)
{
observerService->AddObserver(this, "quit-application-requested", PR_FALSE);
observerService->AddObserver(this, "quit-application-granted", PR_FALSE);
@@ -600,17 +601,17 @@ void nsMsgShutdownService::AttemptShutdo
PR_CNotifyAll(this);
PR_CExitMonitor(this);
}
else
{
nsCOMPtr<nsIAppStartup> appStartup =
do_GetService(NS_APPSTARTUP_CONTRACTID);
NS_ENSURE_TRUE(appStartup, );
- NS_ENSURE_SUCCESS(appStartup->Quit(nsIAppStartup::eAttemptQuit), );
+ NS_ENSURE_SUCCESS(appStartup->Quit(mQuitMode), );
}
}
NS_IMETHODIMP nsMsgShutdownService::SetShutdownListener(nsIWebProgressListener *inListener)
{
NS_ENSURE_TRUE(mMsgProgress, NS_ERROR_FAILURE);
mMsgProgress->RegisterListener(inListener);
return NS_OK;
@@ -702,16 +703,22 @@ NS_IMETHODIMP nsMsgShutdownService::Obse
NS_ENSURE_TRUE(internalDomWin, NS_ERROR_FAILURE); // bail if we don't get a window.
}
}
if (!mQuitForced)
{
nsCOMPtr<nsISupportsPRBool> stopShutdown = do_QueryInterface(aSubject);
stopShutdown->SetData(PR_TRUE);
+
+ // If the attempted quit was a restart, be sure to restart the app once
+ // the tasks have been run. This is usually the case when addons or
+ // updates are going to be installed.
+ if (nsDependentString(aData).EqualsLiteral("restart"))
+ mQuitMode |= nsIAppStartup::eRestart;
}
mMsgProgress->OpenProgressDialog(internalDomWin, topMsgWindow,
"chrome://messenger/content/shutdownWindow.xul",
PR_FALSE, nsnull);
if (mQuitForced)
{
--- a/mailnews/base/src/nsMsgMailSession.h
+++ b/mailnews/base/src/nsMsgMailSession.h
@@ -123,14 +123,15 @@ public:
protected:
nsresult ProcessNextTask();
void AttemptShutdown();
private:
nsCOMArray<nsIMsgShutdownTask> mShutdownTasks;
nsCOMPtr<nsIMsgProgress> mMsgProgress;
PRUint32 mTaskIndex;
+ PRUint32 mQuitMode;
PRPackedBool mProcessedShutdown;
PRPackedBool mQuitForced;
PRPackedBool mReadyToQuit;
};
#endif /* nsMsgMailSession_h__ */
--- a/mailnews/base/src/nsMsgProgress.cpp
+++ b/mailnews/base/src/nsMsgProgress.cpp
@@ -83,17 +83,16 @@ NS_IMETHODIMP nsMsgProgress::OpenProgres
nsresult rv;
if (aMsgWindow)
{
SetMsgWindow(aMsgWindow);
aMsgWindow->SetStatusFeedback(this);
}
- NS_ENSURE_TRUE(!m_dialog, NS_ERROR_ALREADY_INITIALIZED);
NS_ENSURE_ARG_POINTER(dialogURL);
NS_ENSURE_ARG_POINTER(parent);
// Set up window.arguments[0]...
nsCOMPtr<nsISupportsArray> array;
rv = NS_NewISupportsArray(getter_AddRefs(array));
NS_ENSURE_SUCCESS(rv, rv);
@@ -122,24 +121,16 @@ NS_IMETHODIMP nsMsgProgress::OpenProgres
/* void closeProgressDialog (in boolean forceClose); */
NS_IMETHODIMP nsMsgProgress::CloseProgressDialog(PRBool forceClose)
{
m_closeProgress = PR_TRUE;
return OnStateChange(nsnull, nsnull, nsIWebProgressListener::STATE_STOP, forceClose ? NS_ERROR_FAILURE : NS_OK);
}
-/* nsIPrompt GetPrompter (); */
-NS_IMETHODIMP nsMsgProgress::GetPrompter(nsIPrompt **_retval)
-{
- NS_ENSURE_ARG_POINTER(_retval);
- *_retval = nsnull;
- return (! m_closeProgress && m_dialog) ? m_dialog->GetPrompter(_retval) : NS_ERROR_FAILURE;
-}
-
/* attribute boolean processCanceledByUser; */
NS_IMETHODIMP nsMsgProgress::GetProcessCanceledByUser(PRBool *aProcessCanceledByUser)
{
NS_ENSURE_ARG_POINTER(aProcessCanceledByUser);
*aProcessCanceledByUser = m_processCanceled;
return NS_OK;
}
NS_IMETHODIMP nsMsgProgress::SetProcessCanceledByUser(PRBool aProcessCanceledByUser)
--- a/mailnews/base/src/nsMsgProgress.h
+++ b/mailnews/base/src/nsMsgProgress.h
@@ -68,14 +68,13 @@ public:
private:
nsresult ReleaseListeners(void);
PRBool m_closeProgress;
PRBool m_processCanceled;
nsString m_pendingStatus;
PRInt32 m_pendingStateFlags;
PRInt32 m_pendingStateValue;
- nsCOMPtr<nsIDOMWindowInternal> m_dialog;
nsWeakPtr m_msgWindow;
nsCOMArray<nsIWebProgressListener> m_listenerList;
};
#endif // nsMsgProgress_h_
--- a/mailnews/base/src/nsMsgSearchDBView.cpp
+++ b/mailnews/base/src/nsMsgSearchDBView.cpp
@@ -50,16 +50,17 @@
#include "nsIMsgMessageService.h"
#include "nsAutoPtr.h"
#include "nsArrayUtils.h"
#include "nsIMutableArray.h"
#include "nsMsgGroupThread.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsMsgMessageFlags.h"
+#include "nsIMsgSearchSession.h"
static PRBool gReferenceOnlyThreading;
nsMsgSearchDBView::nsMsgSearchDBView()
{
// don't try to display messages for the search pane.
mSuppressMsgDisplay = PR_TRUE;
m_threadsTable.Init();
@@ -731,16 +732,23 @@ nsMsgSearchDBView::OnNewSearch()
NS_IMETHODIMP nsMsgSearchDBView::GetViewType(nsMsgViewTypeValue *aViewType)
{
NS_ENSURE_ARG_POINTER(aViewType);
*aViewType = nsMsgViewType::eShowSearch;
return NS_OK;
}
+NS_IMETHODIMP
+nsMsgSearchDBView::SetSearchSession(nsIMsgSearchSession *aSession)
+{
+ m_searchSession = do_GetWeakReference(aSession);
+ return NS_OK;
+}
+
NS_IMETHODIMP nsMsgSearchDBView::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
{
nsIMsgDatabase *db = static_cast<nsIMsgDatabase *>(instigator);
if (db)
{
db->RemoveListener(this);
m_dbToUseList.RemoveObject(db);
}
--- a/mailnews/base/src/nsMsgSearchDBView.h
+++ b/mailnews/base/src/nsMsgSearchDBView.h
@@ -52,16 +52,18 @@ public:
// these are tied together pretty intimately
friend class nsMsgXFViewThread;
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIMSGSEARCHNOTIFY
NS_DECL_NSIMSGCOPYSERVICELISTENER
+ NS_IMETHOD SetSearchSession(nsIMsgSearchSession *aSearchSession);
+
virtual const char * GetViewName(void) {return "SearchView"; }
NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder,
nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount);
NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow,
nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval);
NS_IMETHOD CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance,
nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater);
NS_IMETHOD Close();
@@ -130,16 +132,17 @@ protected:
PRUint32 mCurIndex;
nsMsgViewIndex* mIndicesForChainedDeleteAndFile;
PRInt32 mTotalIndices;
nsCOMArray<nsIMsgDatabase> m_dbToUseList;
nsMsgViewCommandTypeValue mCommand;
nsCOMPtr <nsIMsgFolder> mDestFolder;
nsString m_curCustomColumn;
+ nsWeakPtr m_searchSession;
nsresult ProcessRequestsInOneFolder(nsIMsgWindow *window);
nsresult ProcessRequestsInAllFolders(nsIMsgWindow *window);
// these are for doing threading of the search hits
// this maps message-ids and reference message ids to
// the corresponding nsMsgXFViewThread object. If we're
--- a/mailnews/base/src/nsMsgThreadedDBView.cpp
+++ b/mailnews/base/src/nsMsgThreadedDBView.cpp
@@ -136,21 +136,21 @@ nsresult nsMsgThreadedDBView::InitThread
m_havePrevView = PR_FALSE;
nsresult getSortrv = NS_OK; // ### TODO m_db->GetSortInfo(&sortType, &sortOrder);
// list all the ids into m_keys.
nsMsgKey startMsg = 0;
do
{
const PRInt32 kIdChunkSize = 400;
- PRInt32 numListed = 0;
- nsMsgKey idArray[kIdChunkSize];
- PRInt32 flagArray[kIdChunkSize];
- char levelArray[kIdChunkSize];
-
+ PRInt32 numListed = 0;
+ nsMsgKey idArray[kIdChunkSize];
+ PRInt32 flagArray[kIdChunkSize];
+ char levelArray[kIdChunkSize];
+
rv = ListThreadIds(&startMsg, (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) != 0, idArray, flagArray,
levelArray, kIdChunkSize, &numListed, nsnull);
if (NS_SUCCEEDED(rv))
{
PRInt32 numAdded = AddKeys(idArray, flagArray, levelArray, m_sortType, numListed);
if (pCount)
*pCount += numAdded;
}
@@ -603,49 +603,49 @@ nsresult nsMsgThreadedDBView::OnNewHeade
PRUint32 msgFlags;
newHdr->GetFlags(&msgFlags);
if ((m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) && !ensureListed && (msgFlags & nsMsgMessageFlags::Read))
return NS_OK;
// Currently, we only add the header in a threaded view if it's a thread.
// We used to check if this was the first header in the thread, but that's
// a bit harder in the unreadOnly view. But we'll catch it below.
- // for search view we don't support threaded display so just add it to the view.
- if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) // || msgHdr->GetMessageKey() == m_messageDB->GetKeyOfFirstMsgInThread(msgHdr->GetMessageKey()))
+ // for search view we don't support threaded display so just add it to the view.
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
rv = AddHdr(newHdr);
- else // need to find the thread we added this to so we can change the hasnew flag
- // added message to existing thread, but not to view
- { // Fix flags on thread header.
+ else // need to find the thread we added this to so we can change the hasnew flag
+ // added message to existing thread, but not to view
+ { // Fix flags on thread header.
PRInt32 threadCount;
PRUint32 threadFlags;
PRBool moveThread = PR_FALSE;
nsMsgViewIndex threadIndex = ThreadIndexOfMsg(newKey, nsMsgViewIndex_None, &threadCount, &threadFlags);
nsCOMPtr <nsIMsgThread> threadHdr;
m_db->GetThreadContainingMsgHdr(newHdr, getter_AddRefs(threadHdr));
if (threadHdr && m_sortType == nsMsgViewSortType::byDate)
{
PRUint32 newestMsgInThread = 0, msgDate = 0;
threadHdr->GetNewestMsgDate(&newestMsgInThread);
newHdr->GetDateInSeconds(&msgDate);
moveThread = (msgDate == newestMsgInThread);
}
if (threadIndex != nsMsgViewIndex_None)
{
- PRUint32 flags = m_flags[threadIndex];
+ PRUint32 flags = m_flags[threadIndex];
if (!(flags & MSG_VIEW_FLAG_HASCHILDREN))
{
flags |= MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD;
if (!(m_viewFlags & nsMsgViewFlagsType::kUnreadOnly))
flags |= nsMsgMessageFlags::Elided;
m_flags[threadIndex] = flags;
}
- if (!(flags & nsMsgMessageFlags::Elided)) // thread is expanded
- { // insert child into thread
+ if (!(flags & nsMsgMessageFlags::Elided)) // thread is expanded
+ { // insert child into thread
// levels of other hdrs may have changed!
- PRUint32 newFlags = msgFlags;
+ PRUint32 newFlags = msgFlags;
PRInt32 level = 0;
nsMsgViewIndex insertIndex = threadIndex;
if (aParentKey == nsMsgKey_None)
{
newFlags |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN;
}
else
{
@@ -659,31 +659,35 @@ nsresult nsMsgThreadedDBView::OnNewHeade
NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
if (aParentKey == nsMsgKey_None)
{
// this header is the new king! try collapsing the existing thread,
// removing it, installing this header as king, and expanding it.
CollapseByIndex(threadIndex, nsnull);
// call base class, so child won't get promoted.
- // nsMsgDBView::RemoveByIndex(threadIndex);
+ // nsMsgDBView::RemoveByIndex(threadIndex);
ExpandByIndex(threadIndex, nsnull);
}
}
else if (aParentKey == nsMsgKey_None)
{
// if we have a collapsed thread which just got a new
// top of thread, change the keys array.
m_keys[threadIndex] = newKey;
}
if (moveThread)
MoveThreadAt(threadIndex);
else
// note change, to update the parent thread's unread and total counts
NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ // if this message is new, and the thread is collapsed, expand it.
+ if (msgFlags & nsMsgMessageFlags::New &&
+ m_flags[threadIndex] & nsMsgMessageFlags::Elided)
+ ExpandByIndex(threadIndex, nsnull);
}
else // adding msg to thread that's not in view.
{
if (threadHdr)
{
AddMsgToThreadNotInView(threadHdr, newHdr, ensureListed);
}
}
--- a/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp
+++ b/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp
@@ -119,23 +119,16 @@ nsMsgXFVirtualFolderDBView::CopyDBView(n
NS_IMETHODIMP nsMsgXFVirtualFolderDBView::GetViewType(nsMsgViewTypeValue *aViewType)
{
NS_ENSURE_ARG_POINTER(aViewType);
*aViewType = nsMsgViewType::eShowVirtualFolderResults;
return NS_OK;
}
-NS_IMETHODIMP
-nsMsgXFVirtualFolderDBView::SetSearchSession(nsIMsgSearchSession *aSession)
-{
- m_searchSession = do_GetWeakReference(aSession);
- return NS_OK;
-}
-
nsresult nsMsgXFVirtualFolderDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool /*ensureListed*/)
{
if (newHdr)
{
PRBool match = PR_FALSE;
nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession);
if (searchSession)
searchSession->MatchHdr(newHdr, m_db, &match);
--- a/mailnews/base/src/nsMsgXFVirtualFolderDBView.h
+++ b/mailnews/base/src/nsMsgXFVirtualFolderDBView.h
@@ -47,17 +47,16 @@
class nsMsgGroupThread;
class nsMsgXFVirtualFolderDBView : public nsMsgSearchDBView
{
public:
nsMsgXFVirtualFolderDBView();
virtual ~nsMsgXFVirtualFolderDBView();
- NS_IMETHOD SetSearchSession(nsIMsgSearchSession *aSearchSession);
// we override all the methods, currently. Might change...
NS_DECL_NSIMSGSEARCHNOTIFY
virtual const char * GetViewName(void) {return "XFVirtualFolderView"; }
NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder,
nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount);
NS_IMETHOD OpenWithHdrs(nsISimpleEnumerator *aHeaders,
nsMsgViewSortTypeValue aSortType,
@@ -87,12 +86,11 @@ protected:
PRUint32 m_cachedFolderArrayIndex; // array index of next folder with cached hits to deal with.
nsCOMArray<nsIMsgFolder> m_foldersSearchingOver;
nsCOMArray<nsIMsgDBHdr> m_hdrHits;
nsCOMPtr <nsIMsgFolder> m_curFolderGettingHits;
PRUint32 m_curFolderStartKeyIndex; // keeps track of the index of the first hit from the cur folder
PRBool m_curFolderHasCachedHits;
PRBool m_doingSearch;
- nsWeakPtr m_searchSession;
};
#endif
--- a/mailnews/base/src/quickSearchManager.js
+++ b/mailnews/base/src/quickSearchManager.js
@@ -176,11 +176,11 @@ var QuickSearchManager = {
term.value = value;
term.attrib = nsMsgSearchAttrib.ToOrCC;
term.op = nsMsgSearchOp.Contains;
term.booleanAnd = false;
searchTerms.push(term);
}
}
- return searchTerms;
+ return searchTerms.length ? searchTerms : null;
}
};
\ No newline at end of file
--- a/mailnews/base/src/searchSpec.js
+++ b/mailnews/base/src/searchSpec.js
@@ -44,35 +44,56 @@ const Cu = Components.utils;
Cu.import("resource://app/modules/iteratorUtils.jsm");
Cu.import("resource://app/modules/quickSearchManager.js");
const nsMsgSearchScope = Ci.nsMsgSearchScope;
const nsIMsgSearchTerm = Ci.nsIMsgSearchTerm;
const nsIMsgLocalMailFolder = Ci.nsIMsgLocalMailFolder;
const nsMsgFolderFlags = Ci.nsMsgFolderFlags;
+const nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
const NS_MSG_SEARCH_INTERRUPTED = 0x00550002;
/**
* Wrapper abstraction around a view's search session. This is basically a
* friend class of FolderDisplayWidget and is privy to some of its internals.
*/
-function SearchSpec(aFolderDisplayWidget) {
- this.owner = aFolderDisplayWidget;
+function SearchSpec(aViewWrapper) {
+ this.owner = aViewWrapper;
this._viewTerms = null;
this._virtualFolderTerms = null;
this._userTerms = null;
this._session = null;
this._sessionListener = null;
this._listenersRegistered = false;
+
+ this._onlineSearch = false;
}
SearchSpec.prototype = {
+ /**
+ * Clone this SearchSpec; intended to be used by DBViewWrapper.clone().
+ */
+ clone: function SearchSpec_clone(aViewWrapper) {
+ let doppel = new SearchSpec(aViewWrapper);
+
+ // we can just copy the terms since we never mutate them
+ doppel._viewTerms = this._viewTerms;
+ doppel._virtualFolderTerms = this._viewTerms;
+ doppel._userTerms = this._viewTerms;
+
+ // _session can stay null
+ // no listener is required, so we can keep _sessionListener and
+ // _listenersRegistered at their default values
+
+ return doppel;
+ },
+
get hasSearchTerms() {
return this._viewTerms || this._virtualFolderTerms || this._userTerms;
},
get hasOnlyVirtualTerms() {
return this._virtualFolderTerms && !this._viewTerms && !this._userTerms;
},
@@ -99,17 +120,17 @@ SearchSpec.prototype = {
* (Potentially) add the db view as a search listener and kick off the search.
* We only do that if we have search terms. The intent is to allow you to
* call this all the time, even if you don't need to.
* DBViewWrapper._applyViewChanges used to handle a lot more of this, but our
* need to make sure that the session listener gets added after the DBView
* caused us to introduce this method. (We want the DB View's OnDone method
* to run before our listener, as it may do important work.)
*/
- associateView: function(aDBView) {
+ associateView: function SearchSpec_associateView(aDBView) {
if (this.hasSearchTerms) {
this.updateSession();
if (!this._sessionListener)
this._sessionListener = new SearchSpecListener(this);
this.session.registerListener(aDBView);
aDBView.searchSession = this._session;
@@ -119,17 +140,17 @@ SearchSpec.prototype = {
this.owner.searching = true;
this.session.search(this.owner.listener.msgWindow);
}
},
/**
* Stop any active search and stop the db view being a search listener (if it
* is one).
*/
- dissociateView: function(aDBView) {
+ dissociateView: function SearchSpec_dissociateView(aDBView) {
// If we are currently searching, interrupt the search. This will
// immediately notify the listeners that the search is done with and
// clear the searching flag for us.
if (this.owner.searching)
this.session.interruptSearch();
if (this._listenersRegistered) {
this._session.unregisterListener(this._sessionListener);
@@ -183,77 +204,109 @@ SearchSpec.prototype = {
set viewTerms(aViewTerms) {
if (aViewTerms)
this._viewTerms = this._groupifyTerms(aViewTerms);
else
this._viewTerms = null;
this.owner._applyViewChanges();
},
/**
+ * @return the view terms currently in effect. Do not mutate this.
+ */
+ get viewTerms() {
+ return this._viewTerms;
+ },
+ /**
* Set search terms that are defined by the 'virtual folder' definition. This
* could also be thought of as the 'saved search' part of a saved search.
*
* @param aVirtualFolderTerms The list of terms. We make our own copy and
* do not mutate yours.
*/
set virtualFolderTerms(aVirtualFolderTerms) {
if (aVirtualFolderTerms)
// we need to clone virtual folder terms because they are pulled from a
// persistent location rather than created on demand
this._virtualFolderTerms = this._groupifyTerms(aVirtualFolderTerms,
true);
else
this._virtualFolderTerms = null;
this.owner._applyViewChanges();
},
+ /**
+ * @return the Virtual folder terms currently in effect. Do not mutate this.
+ */
+ get virtualFolderTerms() {
+ return this._virtualFolderTerms;
+ },
/**
* Set the terms that the user is explicitly searching on. These will be
* augmented with the 'context' search terms potentially provided by
* viewTerms and virtualFolderTerms.
*
* @param aUserTerms The list of terms. We take ownership and mutate it.
*/
set userTerms(aUserTerms) {
if (aUserTerms)
this._userTerms = this._groupifyTerms(aUserTerms);
else
this._userTerms = null;
this.owner._applyViewChanges();
},
/**
+ * @return the user terms currently in effect as set via the |userTerms|
+ * attribute or via the |quickSearch| method. Do not mutate this.
+ */
+ get userTerms() {
+ return this._userTerms;
+ },
+ /**
* Apply a quick-search for the given search mode using the given search
* string. All of the hard work is done by
* QuickSearchManager.createSearchTerms; we mainly just assign the result to
* our userTerms property.
*
* @param aSearchMode One of the QuickSearchConstants.kQuickSearch* search
* mode constants specifying what parts of the message to search on.
* @param aSearchString The search string, consisting of sub-strings delimited
* by '|' to be OR-ed together. Given the string "foo" we search for
* messages containing "foo". Given the string "foo|bar", we search for
* messages containing "foo" or "bar".
*/
quickSearch: function SearchSpec_quickSearch(aSearchMode, aSearchString) {
this.userTerms = QuickSearchManager.createSearchTerms(
- this._session, aSearchMode, aSearchString);
+ this.session, aSearchMode, aSearchString);
},
clear: function SearchSpec_clear() {
if (this.hasSearchTerms) {
this._viewTerms = null;
this._virtualFolderTerms = null;
this._userTerms = null;
this.owner._applyViewChanges();
}
},
get onlineSearch() {
return this._onlineSearch;
},
+ /**
+ * Virtual folders have a concept of 'online search' which affects the logic
+ * in updateSession that builds our search scopes. If onlineSearch is false,
+ * then when displaying the virtual folder unaffected by mail views or quick
+ * searches, we will most definitely perform an offline search. If
+ * onlineSearch is true, we will perform an online search only for folders
+ * which are not available offline and for which the server is configured
+ * to have an online 'searchScope'.
+ * When mail views or quick searches are in effect our search is always
+ * offline unless the only way to satisfy the needs of the constraints is an
+ * online search (read: the message body is required but not available
+ * offline.)
+ */
set onlineSearch(aOnlineSearch) {
this._onlineSearch = aOnlineSearch;
},
/**
* Populate the search session using viewTerms, virtualFolderTerms, and
* userTerms. The way this works is that each of the 'context' sets of
* terms gets wrapped into a group which is boolean anded together with
@@ -261,53 +314,82 @@ SearchSpec.prototype = {
*/
updateSession: function SearchSpec_applySearch() {
let session = this.session;
// clear out our current terms and scope
session.searchTerms.QueryInterface(Ci.nsISupportsArray).Clear();
session.clearScopes();
+ // the scope logic needs to know if any terms look at the body attribute.
+ let haveBodyTerm = false;
+
// -- apply terms
if (this._virtualFolderTerms) {
for each (let term in fixIterator(this._virtualFolderTerms,
nsIMsgSearchTerm)) {
+ if (term.attrib == nsMsgSearchAttrib.Body)
+ haveBodyTerm = true;
session.appendTerm(term);
}
}
if (this._viewTerms) {
for each (let term in fixIterator(this._viewTerms,
nsIMsgSearchTerm)) {
+ if (term.attrib == nsMsgSearchAttrib.Body)
+ haveBodyTerm = true;
session.appendTerm(term);
}
}
if (this._userTerms) {
for each (let term in fixIterator(this._userTerms,
nsIMsgSearchTerm)) {
+ if (term.attrib == nsMsgSearchAttrib.Body)
+ haveBodyTerm = true;
session.appendTerm(term);
}
}
// -- apply scopes
- // if the folder is local, the folder is marked for offline access, or we
- // are offline, then perform an offline search.
- // otherwise, rely on the server's search scope. if we were more thorough,
- // we could try and figure out what operands cause us to fall back toward
- // online usage. (quick search previously specialized this by virtue of
- // having a very limited set of terms in use.)
+ // We are filtering if we have mail view terms or user terms. When
+ // filtering, we bias towards offline search. The only time we would use
+ // an online search when filtering is if one of the constraints uses the
+ // body attribute and the folder is not marked for offline access.
+ // We are not filtering if we only have virtual folder terms, in which case
+ // we honor the onlineSearch attribute. This means that we use the
+ // folder's server's searchScope if the folder is not explicitly marked
+ // offline.
+ // For further discussion on this choice of logic, please read from:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c73
+ let filtering = this._virtualFolderTerms == null &&
+ this._viewTerms == null;
+
let ioService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
for each (let [, folder] in Iterator(this.owner._underlyingFolders)) {
+ // we do not need to check isServer here because _underlyingFolders
+ // filtered it out when it was initialized.
+
let scope;
- if ((folder instanceof nsIMsgLocalMailFolder) ||
- (folder.flags & nsMsgFolderFlags.Offline) ||
- ioService.offline)
+ let folderIsOffline = (folder instanceof nsIMsgLocalMailFolder) ||
+ (folder.flags & nsMsgFolderFlags.Offline) ||
+ ioService.offline;
+ // To restate the above logic into simpler rules, the scope is definitely
+ // offline if:
+ // - Folders available offline always use offline search.
+ // - If we are filtering and don't have a body term, use offline search.
+ // - If we are not filtering and our virtual folder is not marked for
+ // onlineSearch.
+ if (folderIsOffline ||
+ (filtering && !haveBodyTerm) ||
+ (!filtering && !this.onlineSearch))
scope = nsMsgSearchScope.offlineMail;
+ // Otherwise, it's up to the folder's sever's searchScope.
else
scope = folder.server.searchScope;
session.addScopeTerm(scope, folder);
}
},
prettyStringOfSearchTerms: function(aSearchTerms) {
if (aSearchTerms == null)
--- a/mailnews/base/src/virtualFolderWrapper.js
+++ b/mailnews/base/src/virtualFolderWrapper.js
@@ -61,17 +61,19 @@ var VirtualFolderHelper = {
* If the call to addSubfolder fails (and therefore throws), we will NOT catch
* it.
*
* @param aFolderName The name of the new folder to create.
* @param aParentFolder The folder in which to create the search folder.
* @param aSearchFolders A list of nsIMsgFolders that you want to use as the
* sources for the virtual folder OR a string that is the already '|'
* delimited list of folder URIs to use.
- * @param aSearchTerms The search terms to use for the virtual folder.
+ * @param aSearchTerms The search terms to use for the virtual folder. This
+ * should be a JS list/nsIMutableArray/nsISupportsArray of
+ * nsIMsgSearchTermbs.
* @param aOnlineSearch Should the search attempt to use the server's search
* capabilities when possible and appropriate?
*
* @return The VirtualFolderWrapper wrapping the newly created folder. You
* would probably only want this for its virtualFolder attribute which has
* the nsIMsgFolder we created. Be careful about accessing any of the
* other attributes, as they will bring its message database back to life.
*/
@@ -160,70 +162,135 @@ VirtualFolderWrapper.prototype = {
this.dbFolderInfo.getCharProperty("searchFolderUri").split("|");
let folders = [];
for each (let [, folderURI] in Iterator(virtualFolderUris)) {
folders.push(rdfService.GetResource(folderURI)
.QueryInterface(Ci.nsIMsgFolder));
}
return folders;
},
+ /**
+ * Set the search folders that back this virtual folder.
+ *
+ * @param aFolders Either a "|"-delimited string of folder URIs or a list of
+ * nsIMsgFolders that fixIterator can traverse (JS array/nsIMutableArray/
+ * nsISupportsArray).
+ */
set searchFolders(aFolders) {
if (typeof(aFolders) == "string") {
this.dbFolderInfo.setCharProperty("searchFolderUri", aFolders);
}
else {
- let uris = [folder.URI for each ([, folder] in Iterator(aFolders))];
+ let uris = [folder.URI for each (folder in fixIterator(aFolders,
+ Ci.nsIMsgFolder))];
this.dbFolderInfo.setCharProperty("searchFolderUri", uris.join("|"));
}
},
/**
+ * @return a "|"-delimited string containing the URIs of the folders that back
+ * this virtual folder.
+ */
+ get searchFolderURIs() {
+ return this.dbFolderInfo.getCharProperty("searchFolderUri");
+ },
+
+ /**
* @return the list of search terms that define this virtual folder.
*/
get searchTerms() {
+ return this.searchTermsSession.searchTerms;
+ },
+ /**
+ * @return a newly created filter with the search terms loaded into it that
+ * define this virtual folder. The filter is apparently useful as an
+ * nsIMsgSearchSession stand-in to some code.
+ */
+ get searchTermsSession() {
let filterService =
Cc["@mozilla.org/messenger/services/filters;1"]
.getService(Ci.nsIMsgFilterService);
// Temporary means it doesn't get exposed to the UI and doesn't get saved to
// disk. Which is good, because this is just a trick to parse the string
// into search terms.
let filterList = filterService.getTempFilterList(this.virtualFolder);
let tempFilter = filterList.createFilter("temp");
filterList.parseCondition(tempFilter, this.searchString);
- return tempFilter.searchTerms;
+ return tempFilter;
},
+
/**
* Set the search string for this virtual folder to the stringified version of
- * the provided list of search terms.
+ * the provided list of nsIMsgSearchTerm search terms. If you already have
+ * a strinigified version of the search constraint, just set |searchString|
+ * directly.
+ *
+ * @param aTerms Some collection that fixIterator can traverse. A JS list or
+ * XPCOM array (nsIMutableArray or nsISupportsArray) should work.
*/
set searchTerms(aTerms) {
let condition = "";
for (let term in fixIterator(aTerms, Ci.nsIMsgSearchTerm)) {
if (condition.length)
condition += " ";
if (term.matchAll) {
condition = "ALL";
break;
}
condition += (term.booleanAnd) ? "AND (" : "OR (";
condition += term.termAsString + ")";
}
this.searchString = condition;
},
+ /**
+ * @return the set of search terms that define this virtual folder as a
+ * string. You may prefer to use |searchTerms| which converts them
+ * into a list of nsIMsgSearchTerms instead.
+ */
get searchString() {
return this.dbFolderInfo.getCharProperty("searchStr");
},
+ /**
+ * Set the search that defines this virtual folder from a string. If you have
+ * a list of nsIMsgSearchTerms, you should use |searchTerms| instead.
+ */
set searchString(aSearchString) {
this.dbFolderInfo.setCharProperty("searchStr", aSearchString);
},
+ /**
+ * @return whether the virtual folder is configured for online search.
+ */
get onlineSearch() {
return this.dbFolderInfo.getBooleanProperty("searchOnline", false);
},
+ /**
+ * Set whether the virtual folder is configured for online search.
+ */
set onlineSearch(aOnlineSearch) {
this.dbFolderInfo.setBooleanProperty("searchOnline", aOnlineSearch);
},
+ /**
+ * @return the dBFolderInfo associated with the virtual folder directly. May
+ * be null. Will cause the message database to be opened, which may have
+ * memory bloat/leak ramifications, so make sure the folder's database was
+ * already going to be opened anyways or that you call
+ * |cleanUpMessageDatabase|.
+ */
get dbFolderInfo() {
- return this.virtualFolder.msgDatabase.dBFolderInfo;
+ let msgDatabase = this.virtualFolder.msgDatabase;
+ return (msgDatabase && msgDatabase.dBFolderInfo);
+ },
+
+ /**
+ * Avoid memory bloat by making the virtual folder forget about its database.
+ * If the database is actually in use (read: someone is keeping it alive by
+ * having references to it from places other than the nsIMsgFolder), the
+ * folder will be able to re-establish the reference for minimal cost.
+ */
+ cleanUpMessageDatabase:
+ function VirtualFolderWrapper_cleanUpMessageDatabase() {
+ this.virtualFolder.msgDatabase.Close(true);
+ this.virtualFolder.msgDatabase = null;
}
};
new file mode 100644
--- /dev/null
+++ b/mailnews/base/test/unit/tail_base.js
@@ -0,0 +1,1 @@
+load("../../mailnews/resources/mailShutdown.js");
--- a/mailnews/base/test/unit/test_bccInDatabase.js
+++ b/mailnews/base/test/unit/test_bccInDatabase.js
@@ -66,11 +66,12 @@ function run_test()
function continueTest()
{
//dump("\nbccList >" + hdr.bccList);
//dump("\nccList >" + hdr.ccList);
//dump("\n");
do_check_true(hdr.bccList.indexOf("Another Person") >= 0);
do_check_true(hdr.bccList.indexOf("<u1@example.com>") >= 0);
do_check_false(hdr.bccList.indexOf("IDoNotExist") >=0);
+ hdr = null;
do_test_finished();
}
--- a/mailnews/base/test/unit/test_bug428427.js
+++ b/mailnews/base/test/unit/test_bug428427.js
@@ -189,17 +189,20 @@ function testVirtualFolder()
// remove tag from one item to decrease count
var message1 = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
message1.appendElement(hdrs[1], false);
gLocalInboxFolder.removeKeywordsFromMessages(message1, tag1);
do_check_eq(3, virtualFolder.getTotalMessages(false));
do_check_eq(1, virtualFolder.getNumUnread(false));
-
+
+ // End of test, so release our header references
+ hdrs = null;
+
do_test_finished();
return true;
}
// helper functions
// adapted from commandglue.js
function CreateVirtualFolder(newName, parentFolder, searchFolderURIs, searchTerm, searchOnline)
@@ -255,9 +258,8 @@ function makeSearchTerm(aFolder, aStrVal
value.str = aStrValue;
searchTerm.value = value;
searchTerm.attrib = aAttrib;
searchTerm.op = aOp;
searchTerm.booleanAnd = false;
searchSession = null;
return searchTerm;
}
-
--- a/mailnews/base/test/unit/test_bug471682.js
+++ b/mailnews/base/test/unit/test_bug471682.js
@@ -116,11 +116,13 @@ var step5 =
{
var dbSize = gSubfolder.msgDatabase.dBFolderInfo.folderSize;
var dbDate = gSubfolder.msgDatabase.dBFolderInfo.folderDate;
var filePath = gSubfolder.filePath;
var date = parseInt(filePath.lastModifiedTime/1000);
var size = filePath.fileSize;
do_check_eq(size, dbSize);
do_check_eq(date, dbDate);
+ // End of test, so release our header reference
+ gHdr = null;
do_test_finished();
}
}
new file mode 100644
--- /dev/null
+++ b/mailnews/base/test/unit/test_emptyTrash.js
@@ -0,0 +1,171 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+ /*
+ * Test suite for empty trash
+ *
+ * Currently tested:
+ * - Empty local trash
+ * TODO
+ * - Empty imap trash
+ */
+
+// Globals
+var gMsgFile1;
+var gLocalTrashFolder;
+var gCurTestNum;
+var gMsgHdrs = new Array();
+
+const mFNSContractID = "@mozilla.org/messenger/msgnotificationservice;1";
+const nsIMFNService = Ci.nsIMsgFolderNotificationService;
+const nsIMFListener = Ci.nsIMsgFolderListener;
+
+const gCopyService = Cc["@mozilla.org/messenger/messagecopyservice;1"]
+ .getService(Ci.nsIMsgCopyService);
+
+// nsIMsgCopyServiceListener implementation
+var copyListener =
+{
+ OnStartCopy: function() {},
+ OnProgress: function(aProgress, aProgressMax) {},
+ SetMessageKey: function(aKey)
+ {
+ let hdr = gLocalInboxFolder.GetMessageHeader(aKey);
+ gMsgHdrs.push({hdr: hdr, ID: hdr.messageId});
+ },
+ SetMessageId: function(aMessageId) {},
+ OnStopCopy: function(aStatus)
+ {
+ // Check: message successfully copied.
+ do_check_eq(aStatus, 0);
+ // Ugly hack: make sure we don't get stuck in a JS->C++->JS->C++... call stack
+ // This can happen with a bunch of synchronous functions grouped together, and
+ // can even cause tests to fail because they're still waiting for the listener
+ // to return
+ do_timeout(0, "doTest(++gCurTestNum)");
+ }
+};
+
+var urlListener =
+{
+ OnStartRunningUrl: function (aUrl) {
+ },
+ OnStopRunningUrl: function (aUrl, aExitCode) {
+ // Check: message successfully copied.
+ do_check_eq(aExitCode, 0);
+ // Ugly hack: make sure we don't get stuck in a JS->C++->JS->C++... call stack
+ // This can happen with a bunch of synchronous functions grouped together, and
+ // can even cause tests to fail because they're still waiting for the listener
+ // to return
+ do_timeout(0, "doTest(++gCurTestNum)");
+ }
+};
+
+function copyFileMessage(file, destFolder, isDraftOrTemplate)
+{
+ gCopyService.CopyFileMessage(file, destFolder, null, isDraftOrTemplate, 0, "", copyListener, null);
+}
+
+function deleteMessages(srcFolder, items)
+{
+ var array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ items.forEach(function (item) {
+ array.appendElement(item, false);
+ });
+
+ srcFolder.deleteMessages(array, null, false, true, copyListener, true);
+}
+
+/*
+ * TESTS
+ */
+
+// Beware before commenting out a test -- later tests might just depend on earlier ones
+const gTestArray =
+[
+ // Copying message from file
+ function testCopyFileMessage1() { copyFileMessage(gMsgFile1, gLocalInboxFolder, false); },
+
+ // Delete message
+ function testDeleteMessage() { // delete to trash
+ // Let's take a moment to re-initialize stuff that got moved
+ let inboxDB = gLocalInboxFolder.msgDatabase;
+ gMsgHdrs[0].hdr = inboxDB.getMsgHdrForMessageID(gMsgHdrs[0].ID);
+
+ // Now delete the message
+ deleteMessages(gLocalInboxFolder, [gMsgHdrs[0].hdr], false, false);
+ },
+ function emptyTrash()
+ {
+ gRootFolder = gLocalIncomingServer.rootMsgFolder;
+ gLocalTrashFolder = gRootFolder.getChildNamed("Trash");
+ // hold onto a db to make sure that empty trash deals with the case
+ // of someone holding onto the db, but the trash folder has a null db.
+ let gLocalTrashDB = gLocalTrashFolder.msgDatabase;
+ gLocalTrashFolder.msgDatabase = null;
+ // this is synchronous
+ gLocalTrashFolder.emptyTrash(null, null);
+ // check that the trash folder is 0 size, that the db has a 0 message count
+ // and has no messages.
+ do_check_eq(0, gLocalTrashFolder.filePath.fileSize);
+ do_check_eq(0, gLocalTrashFolder.msgDatabase.dBFolderInfo.numMessages);
+ let enumerator = gLocalTrashFolder.msgDatabase.EnumerateMessages();
+ do_check_eq(false, enumerator.hasMoreElements());
+ urlListener.OnStopRunningUrl(null, 0);
+ }
+];
+
+var gMFNService = Cc[mFNSContractID].getService(nsIMFNService);
+
+// Our listener, which captures events.
+function gMFListener() {}
+gMFListener.prototype =
+{
+ folderDeleted: function (aFolder)
+ {
+ aFolder.msgDatabase = null;
+ },
+};
+
+function run_test()
+{
+ loadLocalMailAccount();
+ // Load up a message so that we can copy it in later.
+ gMsgFile1 = do_get_file("../../mailnews/data/bugmail10");
+ // our front end code clears the msg db when it gets told the folder for
+ // an open view has been deleted - so simulate that.
+ var folderDeletedListener = new gMFListener();
+ gMFNService.addListener(folderDeletedListener, nsIMFNService.folderDeleted);
+
+ // "Master" do_test_pending(), paired with a do_test_finished() at the end of all the operations.
+ do_test_pending();
+
+ // Do the test.
+ doTest(1);
+}
+
+function doTest(test)
+{
+ if (test <= gTestArray.length)
+ {
+ gCurTestNum = test;
+
+ var testFn = gTestArray[test-1];
+ // Set a limit of three seconds; if the notifications haven't arrived by then there's a problem.
+ do_timeout(10000, "if (gCurTestNum == "+test+") \
+ do_throw('Notifications not received in 10000 ms for operation "+testFn.name+", current status is '+gCurrStatus);");
+ try {
+ testFn();
+ } catch(ex) {dump(ex);}
+ }
+ else
+ {
+ gMsgHdrs = null;
+ do_test_finished(); // for the one in run_test()
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mailnews/base/test/unit/test_jsTreeSelection.js
@@ -0,0 +1,444 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+Components.utils.import("resource://app/modules/jsTreeSelection.js");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var fakeView = {
+ rowCount: 101,
+ selectionChanged: function() {
+ },
+ QueryInterface: XPCOMUtils.generateQI(
+ [Ci.nsITreeView]),
+};
+
+var fakeBox = {
+ view: fakeView,
+ invalidate: function() {},
+ invalidateRow: function() {},
+ QueryInterface: XPCOMUtils.generateQI(
+ [Ci.nsITreeBoxObject]),
+};
+
+var sel = new JSTreeSelection(fakeBox);
+
+function bad_ranges(aMsg, aExpected) {
+ let s = "\x1b[1;31m!!! BAD RANGES: " + aMsg + "\n";
+ s += "Selection ranges: " + sel._ranges.length + ":";
+ for each (let [,[low,high]] in Iterator(sel._ranges)) {
+ s += " " + low + "-" + high;
+ }
+
+ s += "\nExpected ranges: " + aExpected.length + ":";
+ for (let i = 0; i < aExpected.length; i++) {
+ s += " " + aExpected[i][0] + "-" + aExpected[i][1];
+ }
+
+ s += "\x1b[0m\n";
+
+ dump(s);
+ do_throw(aMsg);
+}
+
+function assert_selection_ranges() {
+ if (sel._ranges.length != arguments.length)
+ bad_ranges("Wrong number of ranges!", arguments);
+
+ let i = 0;
+ let ourCount = 0;
+ for each (let [,[slow,shigh]] in Iterator(sel._ranges)) {
+ let [dlow, dhigh] = arguments[i++];
+ if (dlow != slow || dhigh != shigh)
+ bad_ranges("Range mis-match on index " + i, arguments);
+ ourCount += shigh - slow + 1;
+ }
+
+ if (ourCount != sel.count)
+ bad_ranges("Count was wrong! We counted " + ourCount + " but they say " +
+ sel.count, arguments);
+}
+var asr = assert_selection_ranges;
+
+function assert_current_index(aIndex) {
+ if (sel.currentIndex != aIndex)
+ do_throw("Current index is wrong! Is " + sel.currentIndex +
+ " but should be " + aIndex);
+}
+var aci = assert_current_index;
+
+function assert_shift_pivot(aIndex) {
+ if (sel.shiftSelectPivot != aIndex)
+ do_throw("Current index is wrong! Is " + sel._shiftSelectPivot +
+ " but should be " + aIndex);
+}
+var asp = assert_shift_pivot;
+
+function assert_selected(aIndex) {
+ if (!sel.isSelected(aIndex))
+ do_throw("Index is not selected but should be: " + aIndex);
+}
+var asel = assert_selected;
+
+function assert_not_selected(aIndex) {
+ if (sel.isSelected(aIndex))
+ do_throw("Index is selected but should not be: " + aIndex);
+}
+var ansel = assert_not_selected;
+
+function run_test() {
+ // -- select
+ sel.select(1);
+ asel(1);
+ ansel(0);
+ ansel(2);
+ asr([1,1]);
+ aci(1);
+
+ sel.select(2);
+ asel(2);
+ ansel(1);
+ ansel(3);
+ asr([2,2]);
+ aci(2);
+
+ // -- clearSelection
+ sel.clearSelection();
+ asr();
+ aci(2); // should still be the same...
+
+ // -- toggleSelect
+ // lower fusion
+ sel.select(2);
+ sel.toggleSelect(1);
+ asr([1, 2]);
+ aci(1);
+
+ // upper fusion
+ sel.toggleSelect(3);
+ asr([1, 3]);
+ aci(3);
+
+ // splitting
+ sel.toggleSelect(2);
+ asr([1, 1], [3, 3]);
+ asel(1);
+ asel(3);
+ ansel(0);
+ ansel(2);
+ ansel(4);
+ aci(2);
+
+ // merge
+ sel.toggleSelect(2);
+ asr([1, 3]);
+ aci(2);
+
+ // lower shrinkage
+ sel.toggleSelect(1);
+ asr([2, 3]);
+ aci(1);
+
+ // upper shrinkage
+ sel.toggleSelect(3);
+ asr([2, 2]);
+ aci(3);
+
+ // nukage
+ sel.toggleSelect(2);
+ asr();
+ aci(2);
+
+ // -- rangedSelect
+ // simple non-augment
+ sel.rangedSelect(0, 0, false);
+ asr([0, 0]);
+ asp(0);
+ aci(0);
+
+ // slightly less simple non-augment
+ sel.rangedSelect(2, 4, false);
+ asr([2, 4]);
+ asp(2);
+ aci(4);
+
+ // higher distinct range
+ sel.rangedSelect(7, 9, true);
+ asr([2, 4], [7, 9]);
+ asp(7);
+ aci(9);
+
+ // lower distinct range
+ sel.rangedSelect(0, 0, true);
+ asr([0, 0], [2, 4], [7, 9]);
+ asp(0);
+ aci(0);
+
+ // lower fusion
+ sel.rangedSelect(6, 6, true);
+ asr([0, 0], [2, 4], [6, 9]);
+ asp(6);
+ aci(6);
+
+ // upper fusion
+ sel.rangedSelect(10, 11, true);
+ asr([0, 0], [2, 4], [6, 11]);
+ asp(10);
+ aci(11);
+
+ // notch merge
+ sel.rangedSelect(5, 5, true);
+ asr([0, 0], [2, 11]);
+ asp(5);
+ aci(5);
+
+ // ambiguous consume with merge
+ sel.rangedSelect(0, 5, true);
+ asr([0, 11]);
+ asp(0);
+ aci(5);
+
+ // aligned consumption
+ sel.rangedSelect(0, 15, true);
+ asr([0, 15]);
+ asp(0);
+ aci(15);
+
+ // excessive consumption
+ sel.rangedSelect(5, 7, false);
+ sel.rangedSelect(3, 10, true);
+ asr([3, 10]);
+ asp(3);
+ aci(10);
+
+ // overlap merge
+ sel.rangedSelect(5, 10, false);
+ sel.rangedSelect(15, 20, true);
+ sel.rangedSelect(7, 17, true);
+ asr([5, 20]);
+ asp(7);
+ aci(17);
+
+ // big merge and consume
+ sel.rangedSelect(5, 10, false);
+ sel.rangedSelect(15, 20, true);
+ sel.rangedSelect(25, 30, true);
+ sel.rangedSelect(35, 40, true);
+ sel.rangedSelect(7, 37, true);
+ asr([5, 40]);
+ asp(7);
+ aci(37);
+
+ // broad lower fusion
+ sel.rangedSelect(10, 20, false);
+ sel.rangedSelect(3, 15, true);
+ asr([3, 20]);
+ asp(3);
+ aci(15);
+
+ // -- clearRange
+ sel.rangedSelect(10, 30, false);
+
+ // irrelevant low
+ sel.clearRange(0, 5);
+ asr([10, 30]);
+
+ // irrelevant high
+ sel.clearRange(40, 45);
+ asr([10, 30]);
+
+ // lower shrinkage tight
+ sel.clearRange(10, 10);
+ asr([11, 30]);
+
+ // lower shrinkage broad
+ sel.clearRange(0, 13);
+ asr([14, 30]);
+
+ // upper shrinkage tight
+ sel.clearRange(30, 30);
+ asr([14, 29]);
+
+ // upper shrinkage broad
+ sel.clearRange(27, 50);
+ asr([14, 26]);
+
+ // split tight
+ sel.clearRange(20, 20);
+ asr([14, 19], [21, 26]);
+
+ // split broad
+ sel.toggleSelect(20);
+ sel.clearRange(19, 21);
+ asr([14, 18], [22, 26]);
+
+ // hit two with tight shrinkage
+ sel.clearRange(18, 22);
+ asr([14, 17], [23, 26]);
+
+ // hit two with broad shrinkage
+ sel.clearRange(15, 25);
+ asr([14, 14], [26, 26]);
+
+ // obliterate
+ sel.clearRange(0, 100);
+ asr();
+
+ // multi-obliterate
+ sel.rangedSelect(10, 20, true);
+ sel.rangedSelect(30, 40, true);
+ sel.clearRange(0, 100);
+ asr();
+
+ // obliterate with shrinkage
+ sel.rangedSelect(5, 10, true);
+ sel.rangedSelect(15, 20, true);
+ sel.rangedSelect(25, 30, true);
+ sel.rangedSelect(35, 40, true);
+ sel.clearRange(7, 37);
+ asr([5, 6], [38, 40]);
+
+ // -- selectAll
+ sel.selectAll();
+ asr([0, 100]);
+
+ // -- adjustSelection
+ // bump due to addition on simple select
+ sel.select(5);
+ sel.adjustSelection(5, 1);
+ asr([6, 6]);
+ aci(6);
+
+ sel.select(5);
+ sel.adjustSelection(0, 1);
+ asr([6, 6]);
+ aci(6);
+
+ // bump due to addition on ranged simple select
+ sel.rangedSelect(5, 5, false);
+ sel.adjustSelection(5, 1);
+ asr([6, 6]);
+ asp(6);
+ aci(6);
+
+ sel.rangedSelect(5, 5, false);
+ sel.adjustSelection(0, 1);
+ asr([6, 6]);
+ asp(6);
+ aci(6);
+
+ // bump due to addition on ranged select
+ sel.rangedSelect(5, 7, false);
+ sel.adjustSelection(5, 1);
+ asr([6, 8]);
+ asp(6);
+ aci(8);
+
+ // no-op with addition
+ sel.rangedSelect(0, 3, false);
+ sel.adjustSelection(10, 1);
+ asr([0, 3]);
+ asp(0);
+ aci(3);
+
+ // split due to addition
+ sel.rangedSelect(5, 6, false);
+ sel.adjustSelection(6, 1);
+ asr([5, 5], [7, 7]);
+ asp(5);
+ aci(7);
+
+ // shift due to removal on simple select
+ sel.select(5);
+ sel.adjustSelection(0, -1);
+ asr([4, 4]);
+ aci(4);
+
+ // shift due to removal on ranged simple select
+ sel.rangedSelect(5, 5, false);
+ sel.adjustSelection(0, -1);
+ asr([4, 4]);
+ asp(4);
+ aci(4);
+
+ // nuked due to removal on simple select
+ sel.select(5);
+ sel.adjustSelection(5, -1);
+ asr();
+ aci(-1);
+
+ // upper tight shrinkage due to removal
+ sel.rangedSelect(5, 10, false);
+ sel.adjustSelection(10, -1);
+ asr([5, 9]);
+ asp(5);
+ aci(-1);
+
+ // upper broad shrinkage due to removal
+ sel.rangedSelect(5, 10, false);
+ sel.adjustSelection(6, -10);
+ asr([5, 5]);
+ asp(5);
+ aci(-1);
+
+ // lower tight shrinkage due to removal
+ sel.rangedSelect(5, 10, false);
+ sel.adjustSelection(5, -1);
+ asr([5, 9]);
+ asp(-1);
+ aci(9);
+
+ // lower broad shrinkage due to removal
+ sel.rangedSelect(5, 10, false);
+ sel.adjustSelection(0, -10);
+ asr([0, 0]);
+ asp(-1);
+ aci(0);
+
+ // tight nuke due to removal
+ sel.rangedSelect(5, 10, false);
+ sel.adjustSelection(5, -6);
+ asr();
+ asp(-1);
+ aci(-1);
+
+ // broad nuke due to removal
+ sel.rangedSelect(5, 10, false);
+ sel.adjustSelection(0, -20);
+ asr();
+ asp(-1);
+ aci(-1);
+}
\ No newline at end of file
--- a/mailnews/base/test/unit/test_junkWhitelisting.js
+++ b/mailnews/base/test/unit/test_junkWhitelisting.js
@@ -213,11 +213,14 @@ function continueTest()
spamSettings.initialize(server);
do_check_false(spamSettings.checkWhiteList(hdrs[kDomainTest]));
// stop suppressing whitelist by domain
server.setBoolValue("inhibitWhiteListingIdentityDomain", false);
spamSettings.initialize(server);
do_check_true(spamSettings.checkWhiteList(hdrs[kDomainTest]));
+ // Free our globals
+ hdrs = null;
+
do_test_finished();
}
--- a/mailnews/base/test/unit/test_nsIMsgFolderListenerLocal.js
+++ b/mailnews/base/test/unit/test_nsIMsgFolderListenerLocal.js
@@ -278,12 +278,14 @@ function doTest(test)
var testFn = gTestArray[test-1];
// Set a limit of three seconds; if the notifications haven't arrived by then there's a problem.
do_timeout(10000, "if (gTest == "+test+") \
do_throw('Notifications not received in 10000 ms for operation "+testFn.name+", current status is '+gCurrStatus);");
testFn();
}
else
{
+ gHdrsReceived = null;
+ gMsgHdrs = null;
+ gMFNService.removeListener(gMFListener);
do_test_finished(); // for the one in run_test()
- gMFNService.removeListener(gMFListener);
}
}
--- a/mailnews/base/test/unit/test_searchJunk.js
+++ b/mailnews/base/test/unit/test_searchJunk.js
@@ -252,11 +252,12 @@ function testJunkSearch()
test.attrib,
test.op,
test.count,
testJunkSearch);
}
else
{
testObject = null;
+ hdr = null;
do_test_finished();
}
}
--- a/mailnews/base/test/unit/test_searchTag.js
+++ b/mailnews/base/test/unit/test_searchTag.js
@@ -402,12 +402,13 @@ function testKeywordSearch()
nsMsgSearchAttrib.Keywords,
test.op,
test.count,
testKeywordSearch);
}
else
{
testObject = null;
+ hdr = null;
do_test_finished();
}
}
--- a/mailnews/base/test/unit/test_viewWrapper_logic.js
+++ b/mailnews/base/test/unit/test_viewWrapper_logic.js
@@ -1,13 +1,14 @@
load("../../mailnews/resources/messageGenerator.js");
load("../../mailnews/resources/messageModifier.js");
load("../../mailnews/resources/asyncTestUtils.js");
load("../../mailnews/resources/viewWrapperTestUtils.js");
+initViewWrapperTestUtils();
/**
* Verify that flipping between threading and grouped by sort settings properly
* clears the other flag. (Because they're mutually exclusive, you see.)
*/
function test_threading_grouping_mutual_exclusion () {
let viewWrapper = make_view_wrapper();
let folder = make_empty_folder();
@@ -153,20 +154,64 @@ function test_mailviews_persistence() {
// ...open and verify that it did not take!
yield async_view_open(viewWrapper, folder);
do_check_eq(viewWrapper.mailViewIndex, MailViewConstants.kViewItemAll);
// put the mailview setting back so other tests work
gMockViewWrapperListener.shouldUseMailViews = true;
}
+/**
+ * Make sure:
+ * - View update depth increments / decrements as expected, and triggers a
+ * view rebuild when expected.
+ * - View update depth can't go below zero resulting in odd happenings.
+ * - That the view update depth is zeroed by a close so that we don't
+ * get into awkward states.
+ *
+ * @bug 498145
+ */
+function test_view_update_depth_logic() {
+ let viewWrapper = make_view_wrapper();
+
+ // create an instance-specific dummy method that counts calls t
+ // _applyViewChanges
+ let applyViewCount = 0;
+ viewWrapper._applyViewChanges = function() { applyViewCount++; };
+
+ // - view update depth basics
+ do_check_eq(viewWrapper._viewUpdateDepth, 0);
+ viewWrapper.beginViewUpdate();
+ do_check_eq(viewWrapper._viewUpdateDepth, 1);
+ viewWrapper.beginViewUpdate();
+ do_check_eq(viewWrapper._viewUpdateDepth, 2);
+ viewWrapper.endViewUpdate();
+ do_check_eq(applyViewCount, 0);
+ do_check_eq(viewWrapper._viewUpdateDepth, 1);
+ viewWrapper.endViewUpdate();
+ do_check_eq(applyViewCount, 1);
+ do_check_eq(viewWrapper._viewUpdateDepth, 0);
+
+ // - don't go below zero! (and don't trigger.)
+ applyViewCount = 0;
+ viewWrapper.endViewUpdate();
+ do_check_eq(applyViewCount, 0);
+ do_check_eq(viewWrapper._viewUpdateDepth, 0);
+
+ // - depth zeroed on clear
+ viewWrapper.beginViewUpdate();
+ viewWrapper.close(); // this does little else because there is nothing open
+ do_check_eq(viewWrapper._viewUpdateDepth, 0);
+}
+
var tests = [
test_threading_grouping_mutual_exclusion,
test_sort_primary,
test_sort_secondary_explicit,
test_sort_secondary_implicit,
test_mailviews_persistence,
+ test_view_update_depth_logic,
];
function run_test() {
loadLocalMailAccount();
async_run_tests(tests);
}
--- a/mailnews/base/test/unit/test_viewWrapper_realFolder.js
+++ b/mailnews/base/test/unit/test_viewWrapper_realFolder.js
@@ -4,16 +4,17 @@
* newsgroup specific.)
*/
load("../../mailnews/resources/messageGenerator.js");
load("../../mailnews/resources/messageModifier.js");
load("../../mailnews/resources/asyncTestUtils.js");
load("../../mailnews/resources/viewWrapperTestUtils.js");
+initViewWrapperTestUtils();
/* ===== Real Folder, no features ===== */
/**
* Open a pre-populated real folder, make sure all the messages show up.
*/
function test_real_folder_load() {
let viewWrapper = make_view_wrapper();
@@ -64,30 +65,47 @@ function test_real_folder_load_after_rea
verify_messages_in_view(setOne, viewWrapper);
let [folderTwo, setTwo] = make_folder_with_sets(1);
yield async_view_open(viewWrapper, folderTwo);
verify_messages_in_view(setTwo, viewWrapper);
}
/* ===== Real Folder, Threading Modes ==== */
+/*
+ * The first three tests that verify setting the threading flags has the
+ * expected outcome do this by creating the view from scratch with the view
+ * flags applied. The view threading persistence test handles making sure
+ * that changes in threading on-the-fly work from the perspective of the
+ * bits and what not. None of these are tests of the view implementation's
+ * threading/grouping logic, just sanity checking that we are doing the right
+ * thing.
+ */
+
function test_real_folder_threading_unthreaded() {
let viewWrapper = make_view_wrapper();
let folder = make_empty_folder();
// create a single maximally nested thread.
const count = 10;
let messageSet =
new SyntheticMessageSet(gMessageScenarioFactory.directReply(count));
add_sets_to_folder(folder, [messageSet]);
// verify that we are not threaded (or grouped)
yield async_view_open(viewWrapper, folder);
viewWrapper.beginViewUpdate();
- viewWrapper.showThreaded = false;
+ viewWrapper.showUnthreaded = true;
+ // whitebox test view flags (we've gotten them wrong before...)
+ assert_bit_not_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ "View threaded bit should not be set.");
+ assert_bit_not_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kGroupBySort,
+ "View group-by-sort bit should not be set.");
yield async_view_end_update(viewWrapper);
verify_view_level_histogram({0: count}, viewWrapper);
}
function test_real_folder_threading_threaded() {
let viewWrapper = make_view_wrapper();
let folder = make_empty_folder();
@@ -96,18 +114,29 @@ function test_real_folder_threading_thre
let messageSet =
new SyntheticMessageSet(gMessageScenarioFactory.directReply(count));
add_sets_to_folder(folder, [messageSet]);
// verify that we are threaded (in such a way that we can't be grouped)
yield async_view_open(viewWrapper, folder);
viewWrapper.beginViewUpdate();
viewWrapper.showThreaded = true;
+ // whitebox test view flags (we've gotten them wrong before...)
+ assert_bit_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ "View threaded bit should be set.");
+ assert_bit_not_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kGroupBySort,
+ "View group-by-sort bit should not be set.");
+ // expand everything so our logic below works.
view_expand_all(viewWrapper);
yield async_view_end_update(viewWrapper);
+ // blackbox test view flags: make sure IsContainer is true for the root
+ verify_view_row_at_index_is_container(viewWrapper, 0);
+ // do the histogram test to verify threading...
let expectedHisto = {};
for (let i = 0; i < count; i++)
expectedHisto[i] = 1;
verify_view_level_histogram(expectedHisto, viewWrapper);
}
function test_real_folder_threading_grouped_by_sort() {
let viewWrapper = make_view_wrapper();
@@ -117,28 +146,115 @@ function test_real_folder_threading_grou
const count = 5;
let [folder, messageSet] = make_folder_with_sets([
{count: count, age: {days: 2}, age_incr: {mins: 1}}]);
// group-by-sort sorted by date
yield async_view_open(viewWrapper, folder);
viewWrapper.beginViewUpdate();
viewWrapper.showGroupedBySort = true;
+ // whitebox test view flags (we've gotten them wrong before...)
+ assert_bit_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ "View threaded bit should be set.");
+ assert_bit_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kGroupBySort,
+ "View group-by-sort bit should be set.");
viewWrapper.sort(Ci.nsMsgViewSortType.byDate,
Ci.nsMsgViewSortOrder.ascending);
// expand everyone
view_expand_all(viewWrapper);
yield async_view_end_update(viewWrapper);
// make sure the level depths are correct
verify_view_level_histogram({0: 1, 1: count}, viewWrapper);
// and make sure the first dude is a dummy
verify_view_row_at_index_is_dummy(viewWrapper, 0);
}
+/**
+ * Verify that we the threading modes are persisted. We are only checking
+ * flags here; we trust the previous tests to have done their job.
+ */
+function test_real_folder_threading_persistence() {
+ let viewWrapper = make_view_wrapper();
+ let folder = make_empty_folder();
+
+ // create a single maximally nested thread.
+ const count = 10;
+ let messageSet =
+ new SyntheticMessageSet(gMessageScenarioFactory.directReply(count));
+ add_sets_to_folder(folder, [messageSet]);
+
+ // open the folder, set threaded mode, close it
+ yield async_view_open(viewWrapper, folder);
+ viewWrapper.showThreaded = true; // should be instantaneous
+ verify_view_row_at_index_is_container(viewWrapper, 0);
+ assert_bit_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ "View threaded bit should be set.");
+ assert_bit_not_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kGroupBySort,
+ "View group-by-sort bit should not be set.");
+ viewWrapper.close();
+
+ // open it again, make sure we're threaded, go unthreaded, close
+ yield async_view_open(viewWrapper, folder);
+ assert_true(viewWrapper.showThreaded, "view should be threaded");
+ assert_false(viewWrapper.showUnthreaded, "view is lying about threading");
+ assert_false(viewWrapper.showGroupedBySort, "view is lying about threading");
+ verify_view_row_at_index_is_container(viewWrapper, 0);
+ assert_bit_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ "View threaded bit should be set.");
+ assert_bit_not_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kGroupBySort,
+ "View group-by-sort bit should not be set.");
+
+ viewWrapper.showUnthreaded = true;
+ assert_bit_not_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ "View threaded bit should not be set.");
+ assert_bit_not_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kGroupBySort,
+ "View group-by-sort bit should not be set.");
+ viewWrapper.close();
+
+ // open it again, make sure we're unthreaded, go grouped, close
+ yield async_view_open(viewWrapper, folder);
+ assert_true(viewWrapper.showUnthreaded, "view should be unthreaded");
+ assert_false(viewWrapper.showThreaded, "view is lying about threading");
+ assert_false(viewWrapper.showGroupedBySort, "view is lying about threading");
+ assert_bit_not_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ "View threaded bit should not be set.");
+ assert_bit_not_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kGroupBySort,
+ "View group-by-sort bit should not be set.");
+
+ viewWrapper.showGroupedBySort = true;
+ assert_bit_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ "View threaded bit should be set.");
+ assert_bit_set(viewWrapper._viewFlags, Ci.nsMsgViewFlagsType.kGroupBySort,
+ "View group-by-sort bit should be set.");
+ viewWrapper.close();
+
+ // open it again, make sure we're grouped.
+ yield async_view_open(viewWrapper, folder);
+ assert_true(viewWrapper.showGroupedBySort, "view should be grouped");
+ assert_false(viewWrapper.showThreaded, "view is lying about threading");
+ assert_false(viewWrapper.showUnthreaded, "view is lying about threading");
+ assert_bit_set(viewWrapper._viewFlags,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ "View threaded bit should be set.");
+ assert_bit_set(viewWrapper._viewFlags, Ci.nsMsgViewFlagsType.kGroupBySort,
+ "View group-by-sort bit should be set.");
+}
+
/* ===== Real Folder, View Flags ===== */
/*
* We cannot test the ignored flag for a local folder because we cannot ignore
* threads in a local folder. Only newsgroups can do that and that's not
* easily testable at this time.
*/
@@ -566,16 +682,17 @@ function test_real_folder_special_views_
var tests = [
test_real_folder_load,
test_real_folder_update,
test_real_folder_load_after_real_folder_load,
// - threading modes
test_real_folder_threading_unthreaded,
test_real_folder_threading_threaded,
test_real_folder_threading_grouped_by_sort,
+ test_real_folder_threading_persistence,
// - view flags
// (we cannot test ignored flags in local folders)
test_real_folder_flags_show_unread,
// - mail views: test the actual views
test_real_folder_mail_views_unread,
test_real_folder_mail_views_tags,
test_real_folder_mail_views_not_deleted,
// - mail views: test the custom views
--- a/mailnews/base/test/unit/test_viewWrapper_virtualFolder.js
+++ b/mailnews/base/test/unit/test_viewWrapper_virtualFolder.js
@@ -11,16 +11,17 @@
* We could test all these things, but my patch is way behind schedule...
*/
load("../../mailnews/resources/messageGenerator.js");
load("../../mailnews/resources/messageModifier.js");
load("../../mailnews/resources/asyncTestUtils.js");
load("../../mailnews/resources/viewWrapperTestUtils.js");
+initViewWrapperTestUtils();
/**
* Make sure we open a virtual folder backed by a single underlying folder
* correctly; no constraints.
*/
function test_virtual_folder_single_load_no_pred() {
let viewWrapper = make_view_wrapper();
@@ -238,16 +239,32 @@ function test_virtual_folder_combo_load_
yield async_view_open(viewWrapper, virtTwo);
verify_messages_in_view([twoSubjBar], viewWrapper);
yield async_view_open(viewWrapper, virtOne);
verify_messages_in_view([oneSubjFoo], viewWrapper);
}
/**
+ * Make sure that if a server is listed in a virtual folder's search Uris that
+ * it does not get into our list of _underlyingFolders.
+ */
+function test_virtual_folder_filters_out_servers() {
+ let viewWrapper = make_view_wrapper();
+
+ let [folders] = make_folders_with_sets(2, []);
+ folders.push(folders[0].rootFolder);
+ let virtFolder = make_virtual_folder(folders, {});
+ yield async_view_open(viewWrapper, virtFolder);
+
+ assert_equals(viewWrapper._underlyingFolders.length, 2,
+ "Server folder should have been filtered out.");
+}
+
+/**
* Verify that if one of the folders backing our virtual folder is deleted that
* we do not explode. Then verify that if we remove the rest of them that the
* view wrapper closes itself.
*/
function test_virtual_folder_underlying_folder_deleted() {
let viewWrapper = make_view_wrapper();
let [folderOne, oneSubjFoo, oneNopers] = make_folder_with_sets([
@@ -397,16 +414,18 @@ var tests = [
test_virtual_folder_multi_load_no_pred,
test_virtual_folder_multi_load_simple_pred,
test_virtual_folder_multi_load_complex_pred,
test_virtual_folder_multi_load_alotta_folders_no_pred,
test_virtual_folder_multi_load_alotta_folders_simple_pred,
test_virtual_folder_multi_load_after_load,
// -- mixture of single-backed and multi-backed
test_virtual_folder_combo_load_after_load,
+ // -- ignore things we should ignore
+ test_virtual_folder_filters_out_servers,
// -- rare/edge cases!
test_virtual_folder_underlying_folder_deleted,
// -- mail views (parameterized)
parameterizeTest(test_virtual_folder_mail_views_unread, [1, 4]),
// -- quick search (parameterized for single and multi folder cases)
parameterizeTest(test_virtual_folder_param_quick_search_simple, [1, 4]),
parameterizeTest(test_virtual_folder_param_quick_search_complex, [1, 4]),
// -- mail view with quick search
--- a/mailnews/base/util/Makefile.in
+++ b/mailnews/base/util/Makefile.in
@@ -124,17 +124,20 @@ EXPORTS = \
nsMsgTxn.h \
nsMsgI18N.h \
nsImapMoveCoalescer.h \
nsMsgReadStateTxn.h \
$(NULL)
EXTRA_JS_MODULES = \
folderUtils.jsm \
- iteratorUtils.jsm
+ iteratorUtils.jsm \
+ jsTreeSelection.js \
+ traceHelper.js \
+ $(NULL)
ifndef MOZ_STATIC_MAIL_BUILD
ifdef MOZILLA_INTERNAL_API
EXTRA_DSO_LDOPTS = \
$(LIBS_DIR) \
$(MOZDEPTH)/rdf/util/src/internal/$(LIB_PREFIX)rdfutil_s.$(LIB_SUFFIX) \
$(MOZ_UNICHARUTIL_LIBS) \
new file mode 100644
--- /dev/null
+++ b/mailnews/base/util/jsTreeSelection.js
@@ -0,0 +1,654 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+EXPORTED_SYMBOLS = ['JSTreeSelection'];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+/**
+ * Partial nsITreeSelection implementation so that we can have nsMsgDBViews that
+ * exist only for message display but do not need to be backed by a full
+ * tree view widget. This could also hopefully be used for more xpcshell unit
+ * testing of the FolderDisplayWidget. It might also be useful for creating
+ * transient selections when right-click selection happens.
+ *
+ * Our current limitations:
+ * - We do not support any single selection modes. This is mainly because we
+ * need to look at the box object for that and we don't want to do it.
+ * - Timed selection. Our expected consumers don't use it.
+ *
+ * Our current laziness:
+ * - We aren't very precise about invalidation when it would be potentially
+ * complicated. The theory is that if there is a tree box object, it's
+ * probably native and the XPConnect overhead is probably a lot more than
+ * any potential savings, at least for now when the tree display is
+ * generally C++ XPCOM backed rather than JS XPCOM backed. Also, we
+ * aren't intended to actually be used with a real tree display; you should
+ * be using the C++ object in that case!
+ *
+ * If documentation is omitted for something, it is because we have little to
+ * add to the documentation of nsITreeSelection and really hope that our
+ * documentation tool will copy-down that documentation.
+ *
+ * This implementation attempts to mimic the behavior of nsTreeSelection. In
+ * a few cases, this leads to potentially confusing actions. I attempt to note
+ * when we are doing this and why we do it.
+ *
+ * Unit test is in mailnews/base/util/test_jsTreeSelection.js
+ */
+function JSTreeSelection(aTreeBoxObject) {
+ this._treeBoxObject = aTreeBoxObject;
+
+ this._currentIndex = null;
+ this._shiftSelectPivot = null;
+ this._ranges = [];
+ this._count = 0;
+
+ this._selectEventsSuppressed = false;
+}
+JSTreeSelection.prototype = {
+ /**
+ * The current nsITreeBoxObject, appropriately QueryInterfaced. May be null.
+ */
+ _treeBoxObject: null,
+
+ /**
+ * Where the focus rectangle (that little dotted thing) shows up. Just
+ * because something is focused does not mean it is actually selected.
+ */
+ _currentIndex: null,
+ /**
+ * The view index where the shift is anchored when it is not (conceptually)
+ * the same as _currentIndex. This only happens when you perform a ranged
+ * selection. In that case, the start index of the ranged selection becomes
+ * the shift pivot (and the _currentIndex becomes the end of the ranged
+ * selection.)
+ * It gets cleared whenever the selection changes and it's not the result of
+ * a call to rangedSelect.
+ */
+ _shiftSelectPivot: null,
+ /**
+ * A list of [lowIndexInclusive, highIndexInclusive] non-overlapping,
+ * non-adjacent 'tuples' sort in ascending order.
+ */
+ _ranges: [],
+ /**
+ * The number of currently selected rows.
+ */
+ _count: 0,
+
+ // In the case of the stand-alone message window, there's no tree, but
+ // there's a view.
+ _view: null,
+
+ get tree JSTreeSelection_get_treeBoxObject() {
+ return this._treeBoxObject;
+ },
+ set tree JSTreeSelection_set_treeBoxObject(aTreeBoxObject) {
+ this._treeBoxObject = aTreeBoxObject;
+ },
+
+ set view JSTreeSelection_set_view(aView) {
+ this._view = aView;
+ },
+ /**
+ * Although the nsITreeSelection documentation doesn't say, what this method
+ * is supposed to do is check if the seltype attribute on the XUL tree is any
+ * of the following: "single" (only a single row may be selected at a time,
+ * "cell" (a single cell may be selected), or "text" (the row gets selected
+ * but only the primary column shows up as selected.)
+ *
+ * @return false because we don't support single-selection.
+ */
+ get single JSTreeSelection_get_single() {
+ return false;
+ },
+
+ _updateCount: function JSTreeSelection__updateCount() {
+ this._count = 0;
+ for each (let [, [low, high]] in Iterator(this._ranges)) {
+ this._count += high - low + 1;
+ }
+ },
+
+ get count JSTreeSelection_get_count() {
+ return this._count;
+ },
+
+ isSelected: function JSTreeSelection_isSelected(aViewIndex) {
+ for each (let [,[low, high]] in Iterator(this._ranges)) {
+ if (aViewIndex >= low && aViewIndex <= high)
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Select the given row. It does nothing if that row was already selected.
+ */
+ select: function JSTreeSelection_select(aViewIndex) {
+ // current index will provide our effective shift pivot
+ this._shiftSelectPivot = null;
+ this.currentIndex = aViewIndex;
+
+ if (this._count == 1 && this._ranges[0][0] == aViewIndex)
+ return;
+
+ this._count = 1;
+ this._ranges = [[aViewIndex, aViewIndex]];
+
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+
+ this._fireSelectionChanged();
+ },
+
+ timedSelect: function JSTreeSelection_timedSelect(aIndex, aDelay) {
+ throw new Error("We do not implement timed selection.");
+ },
+
+ toggleSelect: function JSTreeSelection_toggleSelect(aIndex) {
+ this.currentIndex = aIndex;
+ for each (let [iTupe, [low, high]] in Iterator(this._ranges)) {
+ // below the range? add it to the existing range or create a new one
+ if (aIndex < low) {
+ this._count++;
+ // is it just below an existing range? (range fusion only happens in the
+ // high case, not here.)
+ if (aIndex == low - 1) {
+ this._ranges[iTupe][0] = aIndex;
+ break;
+ }
+ // then it gets its own range
+ this._ranges.splice(iTupe, 0, [aIndex, aIndex]);
+ break;
+ }
+ // in the range? will need to either nuke, shrink, or split the range to
+ // remove it
+ if (aIndex >= low && aIndex <= high) {
+ this._count--;
+ // nuke
+ if (aIndex == low && aIndex == high)
+ this._ranges.splice(iTupe, 1);
+ // lower shrink
+ else if (aIndex == low)
+ this._ranges[iTupe][0] = aIndex + 1;
+ // upper shrink
+ else if (aIndex == high)
+ this._ranges[iTupe][1] = aIndex - 1;
+ // split
+ else
+ this._ranges.splice(iTupe, 1, [low, aIndex - 1], [aIndex + 1, high]);
+ break;
+ }
+ // just above the range? fuse into the range, and possibly the next
+ // range up.
+ if (aIndex == high + 1) {
+ this._count++;
+ // see if there is another range and there was just a gap of one between
+ // the two ranges.
+ if ((iTupe + 1 < this._ranges.length) &&
+ (this._ranges[iTupe+1][0] == aIndex + 1)) {
+ // yes, merge the ranges
+ this._ranges.splice(iTupe, 2, [low, this._ranges[iTupe+1][1]]);
+ break;
+ }
+ // nope, no merge required, just update the range
+ this._ranges[iTupe][1] = aIndex;
+ break;
+ }
+ // otherwise we need to keep going
+ }
+
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidateRow(aIndex);
+ this._fireSelectionChanged();
+ },
+
+ /**
+ * @param aRangeStart If omitted, it implies a shift-selection is happening,
+ * in which case we use _shiftSelectPivot as the start if we have it,
+ * _currentIndex if we don't, and if we somehow didn't have a
+ * _currentIndex, we use the range end.
+ * @param aRangeEnd Just the inclusive end of the range.
+ * @param aAugment Does this set a new selection or should it be merged with
+ * the existing selection?
+ */
+ rangedSelect: function JSTreeSelection_rangedSelect(aRangeStart, aRangeEnd,
+ aAugment) {
+ if (aRangeStart == -1) {
+ if (this._shiftSelectPivot != null)
+ aRangeStart = this._shiftSelectPivot;
+ else if (this._currentIndex != null)
+ aRangeStart = this._currentIndex;
+ else
+ aRangeStart = aRangeEnd;
+ }
+
+ this._shiftSelectPivot = aRangeStart;
+ this.currentIndex = aRangeEnd;
+
+ // enforce our ordering constraint for our ranges
+ if (aRangeStart > aRangeEnd)
+ [aRangeStart, aRangeEnd] = [aRangeEnd, aRangeStart];
+
+ // if we're not augmenting, then this is really easy.
+ if (!aAugment) {
+ this._count = aRangeEnd - aRangeStart + 1;
+ this._ranges = [[aRangeStart, aRangeEnd]];
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ this._fireSelectionChanged();
+ return;
+ }
+
+ // Iterate over our existing set of ranges, finding the 'range' of ranges
+ // that our new range overlaps or simply obviates.
+ // Overlap variables track blocks we need to keep some part of, Nuke
+ // variables are for blocks that get spliced out. For our purposes, all
+ // overlap blocks are also nuke blocks.
+ let lowOverlap, lowNuke, highNuke, highOverlap;
+ // in case there is no overlap, also figure an insertionPoint
+ let insertionPoint = this._ranges.length; // default to the end
+ for each (let [iTupe, [low, high]] in Iterator(this._ranges)) {
+ // If it's completely include the range, it should be nuked
+ if (aRangeStart <= low && aRangeEnd >= high) {
+ if (lowNuke == null) // only the first one we see is the low one
+ lowNuke = iTupe;
+ highNuke = iTupe;
+ }
+ // If our new range start is inside a range or is adjacent, it's overlap
+ if (aRangeStart >= low - 1 && aRangeStart <= high + 1 &&
+ lowOverlap == null)
+ lowOverlap = lowNuke = highNuke = iTupe;
+ // If our new range ends inside a range or is adjacent, it's overlap
+ if (aRangeEnd >= low - 1 && aRangeEnd <= high + 1) {
+ highOverlap = highNuke = iTupe;
+ if (lowNuke == null)
+ lowNuke = iTupe;
+ }
+
+ // we're done when no more overlap is possible
+ if (aRangeEnd < low) {
+ insertionPoint = iTupe;
+ break;
+ }
+ }
+
+ if (lowOverlap != null)
+ aRangeStart = Math.min(aRangeStart, this._ranges[lowOverlap][0]);
+ if (highOverlap != null)
+ aRangeEnd = Math.max(aRangeEnd, this._ranges[highOverlap][1]);
+ if (lowNuke != null)
+ this._ranges.splice(lowNuke, highNuke - lowNuke + 1,
+ [aRangeStart, aRangeEnd]);
+ else
+ this._ranges.splice(insertionPoint, 0, [aRangeStart, aRangeEnd]);
+
+ this._updateCount();
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ this._fireSelectionChanged();
+ },
+
+ /**
+ * This is basically RangedSelect but without insertion of a new range and we
+ * don't need to worry about adjacency.
+ * Oddly, nsTreeSelection doesn't fire a selection changed event here...
+ */
+ clearRange: function JSTreeSelection_clearRange(aRangeStart, aRangeEnd) {
+ // Iterate over our existing set of ranges, finding the 'range' of ranges
+ // that our clear range overlaps or simply obviates.
+ // Overlap variables track blocks we need to keep some part of, Nuke
+ // variables are for blocks that get spliced out. For our purposes, all
+ // overlap blocks are also nuke blocks.
+ let lowOverlap, lowNuke, highNuke, highOverlap;
+ for each (let [iTupe, [low, high]] in Iterator(this._ranges)) {
+ // If we completely include the range, it should be nuked
+ if (aRangeStart <= low && aRangeEnd >= high) {
+ if (lowNuke == null) // only the first one we see is the low one
+ lowNuke = iTupe;
+ highNuke = iTupe;
+ }
+ // If our new range start is inside a range, it's nuke and maybe overlap
+ if (aRangeStart >= low && aRangeStart <= high && lowNuke == null) {
+ lowNuke = highNuke = iTupe;
+ // it's only overlap if we don't match at the low end
+ if (aRangeStart > low)
+ lowOverlap = iTupe;
+ }
+ // If our new range ends inside a range, it's nuke and maybe overlap
+ if (aRangeEnd >= low && aRangeEnd <= high) {
+ highNuke = iTupe;
+ // it's only overlap if we don't match at the high end
+ if (aRangeEnd < high)
+ highOverlap = iTupe;
+ if (lowNuke == null)
+ lowNuke = iTupe;
+ }
+
+ // we're done when no more overlap is possible
+ if (aRangeEnd < low)
+ break;
+ }
+ // nothing to do since there's nothing to nuke
+ if (lowNuke == null)
+ return;
+ let args = [lowNuke, highNuke - lowNuke + 1];
+ if (lowOverlap != null)
+ args.push([this._ranges[lowOverlap][0], aRangeStart - 1]);
+ if (highOverlap != null)
+ args.push([aRangeEnd + 1, this._ranges[highOverlap][1]]);
+ this._ranges.splice.apply(this._ranges, args);
+
+ this._updateCount();
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ // note! nsTreeSelection doesn't fire a selection changed event, so neither
+ // do we, but it seems like we should
+ },
+
+ /**
+ * nsTreeSelection always fires a select notification when the range is
+ * cleared, even if there is no effective chance in selection.
+ */
+ clearSelection: function JSTreeSelection_clearSelection() {
+ this._shiftSelectPivot = null;
+ this._count = 0;
+ this._ranges = [];
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ this._fireSelectionChanged();
+ },
+
+ /**
+ * Not even nsTreeSelection implements this.
+ */
+ invertSelection: function JSTreeSelection_invertSelection() {
+ throw new Error("Who really was going to use this?");
+ },
+
+ /**
+ * Select all with no rows is a no-op, otherwise we select all and notify.
+ */
+ selectAll: function JSTreeSelection_selectAll() {
+ if (!this._treeBoxObject)
+ return;
+
+ let view = this._treeBoxObject.view.QueryInterface(Ci.nsITreeView);
+ let rowCount = view.rowCount;
+
+ // no-ops-ville
+ if (!rowCount)
+ return;
+
+ this._count = rowCount;
+ this._ranges = [[0, rowCount - 1]];
+
+ this._treeBoxObject.invalidate();
+ this._fireSelectionChanged();
+ },
+
+ getRangeCount: function JSTreeSelection_getRangeCount() {
+ return this._ranges.length;
+ },
+ getRangeAt: function JSTreeSelection_getRangeAt(aRangeIndex, aMinObj,
+ aMaxObj) {
+ if (aRangeIndex < 0 || aRangeIndex > this._ranges.length)
+ throw new Exception("Try a real range index next time.");
+ [aMinObj.value, aMaxObj.value] = this._ranges[aRangeIndex];
+ },
+
+ invalidateSelection: function JSTreeSelection_invalidateSelection() {
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ },
+
+ /**
+ * Helper method to adjust points in the face of row additions/removal.
+ * @param aPoint The point, null if there isn't one, or an index otherwise.
+ * @param aDeltaAt The row at which the change is happening.
+ * @param aDelta The number of rows added if positive, or the (negative)
+ * number of rows removed.
+ */
+ _adjustPoint: function JSTreeSelection__adjustPoint(aPoint, aDeltaAt,
+ aDelta) {
+ // if there is no point, no change
+ if (aPoint == null)
+ return aPoint;
+ // if the point is before the change, no change
+ if (aPoint < aDeltaAt)
+ return aPoint;
+ // if it's a deletion and it includes the point, clear it
+ if (aDelta < 0 && aPoint >= aDeltaAt && (aPoint + aDelta < aDeltaAt))
+ return null;
+ // (else) the point is at/after the change, compensate
+ return aPoint + aDelta;
+ },
+ /**
+ * Find the index of the range, if any, that contains the given index, and
+ * the index at which to insert a range if one does not exist.
+ *
+ * @return A tuple containing: 1) the index if there is one, null otherwise,
+ * 2) the index at which to insert a range that would contain the point.
+ */
+ _findRangeContainingRow:
+ function JSTreeSelection__findRangeContainingRow(aIndex) {
+ for each (let [iTupe, [low, high]] in Iterator(this._ranges)) {
+ if (aIndex >= low && aIndex <= high)
+ return [iTupe, iTupe];
+ if (aIndex < low)
+ return [null, iTupe];
+ }
+ return [null, this._ranges.length];
+ },
+
+
+ /**
+ * When present, a list of calls made to adjustSelection. See
+ * |logAdjustSelectionForReplay| and |replayAdjustSelectionLog|.
+ */
+ _adjustSelectionLog: null,
+ /**
+ * Start logging calls to adjustSelection made against this instance. You
+ * would do this because you are replacing an existing selection object
+ * with this instance for the purposes of creating a transient selection.
+ * Of course, you want the original selection object to be up-to-date when
+ * you go to put it back, so then you can call replayAdjustSelectionLog
+ * with that selection object and everything will be peachy.
+ */
+ logAdjustSelectionForReplay:
+ function JSTreeSelection_logAdjustSelectionForReplay() {
+ this._adjustSelectionLog = [];
+ },
+ /**
+ * Stop logging calls to adjustSelection and replay the existing log against
+ * aSelection.
+ *
+ * @param aSelection {nsITreeSelection}.
+ */
+ replayAdjustSelectionLog:
+ function JSTreeSelection_replayAdjustSelectionLog(aSelection) {
+ if (this._adjustSelectionLog.length) {
+ // Temporarily disable selection events because adjustSelection is going
+ // to generate an event each time otherwise, and better 1 event than
+ // many.
+ aSelection.selectEventsSuppressed = true;
+ for each (let [, [index, count]] in Iterator(this._adjustSelectionLog)) {
+ aSelection.adjustSelection(index, count);
+ }
+ aSelection.selectEventsSuppressed = false;
+ }
+ this._adjustSelectionLog = null;
+ },
+
+ adjustSelection: function JSTreeSelection_adjustSelection(aIndex, aCount) {
+ // nothing to do if there is no actual change
+ if (!aCount)
+ return;
+
+ if (this._adjustSelectionLog)
+ this._adjustSelectionLog.push([aIndex, aCount]);
+
+ // adjust our points
+ this._shiftSelectPivot = this._adjustPoint(this._shiftSelectPivot,
+ aIndex, aCount);
+ this._currentIndex = this._adjustPoint(this._currentIndex, aIndex, aCount);
+
+ // If we are adding rows, we want to split any range at aIndex and then
+ // translate all of the ranges above that point up.
+ if (aCount > 0) {
+ let [iContain, iInsert] = this._findRangeContainingRow(aIndex);
+ if (iContain != null) {
+ let [low, high] = this._ranges[iContain];
+ // if it is the low value, we just want to shift the range entirely, so
+ // do nothing (and keep iInsert pointing at it for translation)
+ // if it is not the low value, then there must be at least two values so
+ // we should split it and only translate the new/upper block
+ if (aIndex != low) {
+ this._ranges.splice(iContain, 1, [low, aIndex - 1], [aIndex, high]);
+ iInsert++;
+ }
+ }
+ // now translate everything from iInsert on up
+ for (let iTrans = iInsert; iTrans < this._ranges.length; iTrans++) {
+ let [low, high] = this._ranges[iTrans];
+ this._ranges[iTrans] = [low + aCount, high + aCount];
+ }
+ // invalidate and fire selection change notice
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ this._fireSelectionChanged();
+ return;
+ }
+
+ // If we are removing rows, we are basically clearing the range that is
+ // getting deleted and translating everyone above the remaining point
+ // downwards. The one trick is we may have to merge the lowest translated
+ // block.
+ let saveSuppress = this.selectEventsSuppressed;
+ this.selectEventsSuppressed = true;
+ this.clearRange(aIndex, aIndex - aCount - 1);
+ // translate
+ let iTrans = this._findRangeContainingRow(aIndex)[1];
+ for (; iTrans < this._ranges.length; iTrans++) {
+ let [low, high] = this._ranges[iTrans];
+ // for the first range, low may be below the index, in which case it
+ // should not get translated
+ this._ranges[iTrans] = [(low >= aIndex) ? low + aCount : low,
+ high + aCount];
+ }
+ // we may have to merge the lowest translated block because it may now be
+ // adjacent to the previous block
+ if (iTrans > 0 && iTrans < this._ranges.length &&
+ this._ranges[iTrans-1][1] == this_ranges[iTrans][0]) {
+ this._ranges[iTrans-1][1] = this._ranges[iTrans][1];
+ this._ranges.splice(iTrans, 1);
+ }
+
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidate();
+ this.selectEventsSuppressed = saveSuppress;
+ },
+
+ get selectEventsSuppressed JSTreeSelection_get_selectEventsSuppressed() {
+ return this._selectEventsSuppressed;
+ },
+ /**
+ * Control whether selection events are suppressed. For consistency with
+ * nsTreeSelection, we always generate a selection event when a value of
+ * false is assigned, even if the value was already false.
+ */
+ set selectEventsSuppressed
+ JSTreeSelection_set_selectEventsSuppressed(aSuppress) {
+ this._selectEventsSuppressed = aSuppress;
+ if (!aSuppress)
+ this._fireSelectionChanged();
+ },
+
+ /**
+ * Note that we bypass any XUL "onselect" handler that may exist and go
+ * straight to the view. If you have a tree, you shouldn't be using us,
+ * so this seems aboot right.
+ */
+ _fireSelectionChanged: function JSTreeSelection__fireSelectionChanged() {
+ // don't fire if we are suppressed; we will fire when un-suppressed
+ if (this.selectEventsSuppressed)
+ return;
+ let view;
+ if (this._treeBoxObject && this._treeBoxObject.view)
+ view = this._treeBoxObject.view;
+ else
+ view = this._view;
+
+ view = view.QueryInterface(Ci.nsITreeView);
+ view.selectionChanged();
+ },
+
+ get currentIndex JSTreeSelection_get_currentIndex() {
+ if (this._currentIndex == null)
+ return -1;
+ return this._currentIndex;
+ },
+ /**
+ * Sets the current index. Other than updating the variable, this just
+ * invalidates the tree row if we have a tree.
+ * The real selection object would send a DOM event we don't care about.
+ */
+ set currentIndex JSTreeSelection_set_currentIndex(aIndex) {
+ if (aIndex == this.currentIndex)
+ return;
+
+ this._currentIndex = (aIndex != -1) ? aIndex : null;
+ if (this._treeBoxObject)
+ this._treeBoxObject.invalidateRow(aIndex);
+ },
+
+ currentColumn: null,
+
+ get shiftSelectPivot JSTreeSelection_get_shiftSelectPivot() {
+ return this._shiftSelectPivot != null ? this._shiftSelectPivot : -1;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI(
+ [Ci.nsITreeSelection]),
+};
--- a/mailnews/base/util/nsMsgDBFolder.cpp
+++ b/mailnews/base/util/nsMsgDBFolder.cpp
@@ -1112,16 +1112,21 @@ NS_IMETHODIMP nsMsgDBFolder::OnAnnouncer
else if (mDatabase)
{
mDatabase->RemoveListener(this);
mDatabase = nsnull;
}
return NS_OK;
}
+NS_IMETHODIMP nsMsgDBFolder::OnEvent(nsIMsgDatabase *aDB, const char *aEvent)
+{
+ return NS_OK;
+}
+
NS_IMETHODIMP nsMsgDBFolder::GetManyHeadersToDownload(PRBool *retval)
{
NS_ENSURE_ARG_POINTER(retval);
PRInt32 numTotalMessages;
// is there any reason to return false?
if (!mDatabase)
*retval = PR_TRUE;
@@ -1911,21 +1916,30 @@ nsMsgDBFolder::MatchOrChangeFilterDestin
{
nsCOMPtr <nsIMsgIncomingServer> server = do_QueryElementAt(allServers, serverIndex);
if (server)
{
PRBool canHaveFilters;
rv = server->GetCanHaveFilters(&canHaveFilters);
if (NS_SUCCEEDED(rv) && canHaveFilters)
{
+ // update the filterlist to match the new folder name
rv = server->GetFilterList(nsnull, getter_AddRefs(filterList));
- if (filterList)
+ if (NS_SUCCEEDED(rv) && filterList)
{
rv = filterList->MatchOrChangeFilterTarget(oldUri, newUri, caseInsensitive, found);
- if (found && newFolder && !newUri.IsEmpty())
+ if (NS_SUCCEEDED(rv) && found && newFolder && !newUri.IsEmpty())
+ rv = filterList->SaveToDefaultFile();
+ }
+ // update the editable filterlist to match the new folder name
+ rv = server->GetEditableFilterList(nsnull, getter_AddRefs(filterList));
+ if (NS_SUCCEEDED(rv) && filterList)
+ {
+ rv = filterList->MatchOrChangeFilterTarget(oldUri, newUri, caseInsensitive, found);
+ if (NS_SUCCEEDED(rv) && found && newFolder && !newUri.IsEmpty())
rv = filterList->SaveToDefaultFile();
}
}
}
}
return rv;
}
@@ -3936,23 +3950,25 @@ void nsMsgDBFolder::ChangeNumPendingTota
if (NS_SUCCEEDED(rv) && folderInfo)
folderInfo->SetImapTotalPendingMessages(mNumPendingTotalMessages);
NotifyIntPropertyChanged(kTotalMessagesAtom, oldTotalMessages, newTotalMessages);
}
}
NS_IMETHODIMP nsMsgDBFolder::SetFlag(PRUint32 flag)
{
+ // If calling this function causes us to open the db (i.e., it was not
+ // open before), we're going to close the db before returning.
+ PRBool dbWasOpen = mDatabase != nsnull;
+
ReadDBFolderInfo(PR_FALSE);
// OnFlagChange can be expensive, so don't call it if we don't need to
PRBool flagSet;
nsresult rv;
- PRBool dbWasOpen = mDatabase != nsnull;
-
if (NS_FAILED(rv = GetFlag(flag, &flagSet)))
return rv;
if (!flagSet)
{
mFlags |= flag;
OnFlagChange(flag);
}
@@ -4733,16 +4749,35 @@ NS_IMETHODIMP
nsMsgDBFolder::SetFilterList(nsIMsgFilterList *aFilterList)
{
nsCOMPtr<nsIMsgIncomingServer> server;
nsresult rv = GetServer(getter_AddRefs(server));
NS_ENSURE_SUCCESS(rv, rv);
return server->SetFilterList(aFilterList);
}
+NS_IMETHODIMP
+nsMsgDBFolder::GetEditableFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetEditableFilterList(aMsgWindow, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetEditableFilterList(nsIMsgFilterList *aFilterList)
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->SetEditableFilterList(aFilterList);
+}
+
/* void enableNotifications (in long notificationType, in boolean enable); */
NS_IMETHODIMP nsMsgDBFolder::EnableNotifications(PRInt32 notificationType, PRBool enable, PRBool dbBatching)
{
if (notificationType == nsIMsgFolder::allMessageCountNotifications)
{
mNotifyCountChanges = enable;
// start and stop db batching here. This is under the theory
// that any time we want to enable and disable notifications,
--- a/mailnews/base/util/nsMsgIncomingServer.cpp
+++ b/mailnews/base/util/nsMsgIncomingServer.cpp
@@ -1032,25 +1032,48 @@ nsMsgIncomingServer::SetFilterList(nsIMs
{
mFilterList = aFilterList;
return NS_OK;
}
NS_IMETHODIMP
nsMsgIncomingServer::GetFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
{
+ NS_ENSURE_ARG_POINTER(aResult);
if (!mFilterList)
{
nsCOMPtr<nsIMsgFolder> msgFolder;
// use GetRootFolder so for deferred pop3 accounts, we'll get the filters
// file from the deferred account, not the deferred to account,
// so that filters will still be per-server.
nsresult rv = GetRootFolder(getter_AddRefs(msgFolder));
NS_ENSURE_SUCCESS(rv, rv);
+ nsCString filterType;
+ rv = GetCharValue("filter.type", filterType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!filterType.IsEmpty() && !filterType.EqualsLiteral("default"))
+ {
+ nsCAutoString contractID("@mozilla.org/filterlist;1?type=");
+ contractID += filterType;
+ ToLowerCase(contractID);
+ mFilterList = do_CreateInstance(contractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mFilterList->SetFolder(msgFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aResult = mFilterList);
+ return NS_OK;
+ }
+
+ // The default case, a local folder, is a bit special. It requires
+ // more initialization.
+
nsCOMPtr<nsILocalFile> thisFolder;
rv = msgFolder->GetFilePath(getter_AddRefs(thisFolder));
NS_ENSURE_SUCCESS(rv, rv);
mFilterFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mFilterFile->InitWithFile(thisFolder);
NS_ENSURE_SUCCESS(rv, rv);
@@ -1079,17 +1102,62 @@ nsMsgIncomingServer::GetFilterList(nsIMs
NS_ENSURE_SUCCESS(rv, rv);
rv = filterService->OpenFilterList(mFilterFile, msgFolder, aMsgWindow, getter_AddRefs(mFilterList));
NS_ENSURE_SUCCESS(rv, rv);
}
NS_IF_ADDREF(*aResult = mFilterList);
return NS_OK;
+}
+NS_IMETHODIMP
+nsMsgIncomingServer::SetEditableFilterList(nsIMsgFilterList *aEditableFilterList)
+{
+ mEditableFilterList = aEditableFilterList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetEditableFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (!mEditableFilterList)
+ {
+ PRBool editSeparate;
+ nsresult rv = GetBoolValue("filter.editable.separate", &editSeparate);
+ if (NS_FAILED(rv) || !editSeparate)
+ return GetFilterList(aMsgWindow, aResult);
+
+ nsCString filterType;
+ rv = GetCharValue("filter.editable.type", filterType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCAutoString contractID("@mozilla.org/filterlist;1?type=");
+ contractID += filterType;
+ ToLowerCase(contractID);
+ mEditableFilterList = do_CreateInstance(contractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ // use GetRootFolder so for deferred pop3 accounts, we'll get the filters
+ // file from the deferred account, not the deferred to account,
+ // so that filters will still be per-server.
+ rv = GetRootFolder(getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mEditableFilterList->SetFolder(msgFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aResult = mEditableFilterList);
+ return NS_OK;
+ }
+
+ NS_IF_ADDREF(*aResult = mEditableFilterList);
+ return NS_OK;
}
// If the hostname contains ':' (like hostname:1431)
// then parse and set the port number.
nsresult
nsMsgIncomingServer::InternalSetHostName(const nsACString& aHostname, const char * prefName)
{
nsCString hostname;
--- a/mailnews/base/util/nsMsgIncomingServer.h
+++ b/mailnews/base/util/nsMsgIncomingServer.h
@@ -95,16 +95,17 @@ protected:
virtual nsresult CreateRootFolderFromUri(const nsCString &serverUri,
nsIMsgFolder **rootFolder) = 0;
nsresult InternalSetHostName(const nsACString& aHostname, const char * prefName);
nsresult getProtocolInfo(nsIMsgProtocolInfo **aResult);
nsCOMPtr <nsILocalFile> mFilterFile;
nsCOMPtr <nsIMsgFilterList> mFilterList;
+ nsCOMPtr <nsIMsgFilterList> mEditableFilterList;
nsCOMPtr<nsIPrefBranch> mPrefBranch;
nsCOMPtr<nsIPrefBranch> mDefPrefBranch;
// these allow us to handle duplicate incoming messages, e.g. delete them.
nsDataHashtable<nsCStringHashKey,PRInt32> m_downloadedHdrs;
PRInt32 m_numMsgsDownloaded;
static PLDHashOperator evictOldEntries(nsCStringHashKey::KeyType aKey, PRInt32 &aData, void *aClosure);
private:
--- a/mailnews/base/util/nsMsgMailNewsUrl.cpp
+++ b/mailnews/base/util/nsMsgMailNewsUrl.cpp
@@ -60,16 +60,17 @@ nsMsgMailNewsUrl::nsMsgMailNewsUrl()
{
// nsIURI specific state
m_errorMessage = nsnull;
m_runningUrl = PR_FALSE;
m_updatingFolder = PR_FALSE;
m_addContentToCache = PR_FALSE;
m_msgIsInLocalCache = PR_FALSE;
m_suppressErrorMsgs = PR_FALSE;
+ mMaxProgress = -1;
m_baseURL = do_CreateInstance(NS_STANDARDURL_CONTRACTID);
}
#define NOTIFY_URL_LISTENERS(propertyfunc_, params_) \
PR_BEGIN_MACRO \
nsTObserverArray<nsCOMPtr<nsIUrlListener> >::ForwardIterator iter(mUrlListeners); \
while (iter.HasMore()) { \
@@ -248,16 +249,28 @@ NS_IMETHODIMP nsMsgMailNewsUrl::GetStatu
NS_IMETHODIMP nsMsgMailNewsUrl::SetStatusFeedback(nsIMsgStatusFeedback *aMsgFeedback)
{
if (aMsgFeedback)
m_statusFeedbackWeak = do_GetWeakReference(aMsgFeedback);
return NS_OK;
}
+NS_IMETHODIMP nsMsgMailNewsUrl::GetMaxProgress(PRInt64 *aMaxProgress)
+{
+ *aMaxProgress = mMaxProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetMaxProgress(PRInt64 aMaxProgress)
+{
+ mMaxProgress = aMaxProgress;
+ return NS_OK;
+}
+
NS_IMETHODIMP nsMsgMailNewsUrl::GetLoadGroup(nsILoadGroup **aLoadGroup)
{
*aLoadGroup = nsnull;
// note: it is okay to return a null load group and not return an error
// it's possible the url really doesn't have load group
nsCOMPtr<nsILoadGroup> loadGroup (do_QueryReferent(m_loadGroupWeak));
if (!loadGroup)
{
--- a/mailnews/base/util/nsMsgMailNewsUrl.h
+++ b/mailnews/base/util/nsMsgMailNewsUrl.h
@@ -87,16 +87,17 @@ protected:
nsWeakPtr m_loadGroupWeak;
nsCOMPtr<nsIMimeHeaders> mMimeHeaders;
nsCOMPtr<nsIMsgSearchSession> m_searchSession;
nsCOMPtr<nsICacheEntryDescriptor> m_memCacheEntry;
nsCOMPtr<nsICacheSession> m_imageCacheSession;
nsCOMPtr<nsISupportsArray> m_cachedMemCacheEntries;
nsCOMPtr<nsIMsgHeaderSink> mMsgHeaderSink;
char *m_errorMessage;
+ PRInt64 mMaxProgress;
PRBool m_runningUrl;
PRBool m_updatingFolder;
PRBool m_addContentToCache;
PRBool m_msgIsInLocalCache;
PRBool m_suppressErrorMsgs;
// the following field is really a bit of a hack to make
// open attachments work. The external applications code sometimes tries to figure out the right
--- a/mailnews/base/util/nsMsgProtocol.cpp
+++ b/mailnews/base/util/nsMsgProtocol.cpp
@@ -82,16 +82,17 @@ static PRUnichar *FormatStringWithHostNa
nsMsgProtocol::nsMsgProtocol(nsIURI * aURL)
{
m_flags = 0;
m_readCount = 0;
mLoadFlags = 0;
m_socketIsOpen = PR_FALSE;
+ mContentLength = -1;
GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "tempMessage.eml",
getter_AddRefs(m_tempMsgFile));
mSuppressListenerNotifications = PR_FALSE;
InitFromURI(aURL);
}
@@ -634,17 +635,23 @@ NS_IMETHODIMP nsMsgProtocol::GetContentC
NS_IMETHODIMP nsMsgProtocol::SetContentCharset(const nsACString &aContentCharset)
{
NS_WARNING("nsMsgProtocol::SetContentCharset() not implemented");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP nsMsgProtocol::GetContentLength(PRInt32 * aContentLength)
{
- *aContentLength = -1;
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetContentLength(PRInt32 aContentLength)
+{
+ mContentLength = aContentLength;
return NS_OK;
}
NS_IMETHODIMP nsMsgProtocol::GetSecurityInfo(nsISupports * *aSecurityInfo)
{
*aSecurityInfo = nsnull;
return NS_ERROR_NOT_IMPLEMENTED;
}
@@ -652,23 +659,16 @@ NS_IMETHODIMP nsMsgProtocol::GetSecurity
NS_IMETHODIMP nsMsgProtocol::GetName(nsACString &result)
{
if (m_url)
return m_url->GetSpec(result);
result.Truncate();
return NS_OK;
}
-
-NS_IMETHODIMP
-nsMsgProtocol::SetContentLength(PRInt32 aContentLength)
-{
- return NS_ERROR_NOT_IMPLEMENTED;
-}
-
NS_IMETHODIMP nsMsgProtocol::GetOwner(nsISupports * *aPrincipal)
{
*aPrincipal = mOwner;
NS_IF_ADDREF(*aPrincipal);
return NS_OK;
}
NS_IMETHODIMP nsMsgProtocol::SetOwner(nsISupports * aPrincipal)
--- a/mailnews/base/util/nsMsgProtocol.h
+++ b/mailnews/base/util/nsMsgProtocol.h
@@ -176,16 +176,17 @@ protected:
nsCOMPtr<nsIStreamListener> m_channelListener;
nsCOMPtr<nsISupports> m_channelContext;
nsCOMPtr<nsILoadGroup> m_loadGroup;
nsLoadFlags mLoadFlags;
nsCOMPtr<nsIProgressEventSink> mProgressEventSink;
nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
nsCOMPtr<nsISupports> mOwner;
nsCString m_ContentType;
+ PRInt32 mContentLength;
nsCString m_lastPasswordSent; // used to prefill the password prompt
// private helper routine used by subclasses to quickly get a reference to the correct prompt dialog
// for a mailnews url.
nsresult GetPromptDialogFromUrl(nsIMsgMailNewsUrl * aMsgUrl, nsIPrompt ** aPromptDialog);
// if a url isn't going to result in any content then we want to suppress calls to
--- a/mailnews/base/util/nsMsgUtils.cpp
+++ b/mailnews/base/util/nsMsgUtils.cpp
@@ -77,16 +77,17 @@
#include "nsIMsgAccountManager.h"
#include "nsIOutputStream.h"
#include "nsMsgFileStream.h"
#include "nsIFileURL.h"
#include "nsNetUtil.h"
#include "nsIMsgDatabase.h"
#include "nsIMutableArray.h"
#include "nsIMsgMailNewsUrl.h"
+#include "nsArrayUtils.h"
static NS_DEFINE_CID(kImapUrlCID, NS_IMAPURL_CID);
static NS_DEFINE_CID(kCMailboxUrl, NS_MAILBOXURL_CID);
static NS_DEFINE_CID(kCNntpUrlCID, NS_NNTPURL_CID);
#define ILLEGAL_FOLDER_CHARS ";#"
#define ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER "."
#define ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER ".~ "
@@ -1399,76 +1400,48 @@ PRBool MsgHostDomainIsTrusted(nsCString
domain = end + 1;
} while (*end);
return domainIsTrusted;
}
nsresult FolderUriFromDirInProfile(nsILocalFile *aLocalPath, nsACString &mailboxUri)
{
-
nsresult rv;
nsCOMPtr<nsIMsgAccountManager> accountManager =
do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
-
NS_ENSURE_SUCCESS(rv, rv);
- nsCOMPtr<nsISupportsArray> serverArray;
- accountManager->GetAllServers(getter_AddRefs(serverArray));
-
- PRUint32 cnt;
- rv = serverArray->Count(&cnt);
- NS_ENSURE_SUCCESS(rv, rv);
- PRInt32 count = cnt;
- PRInt32 i;
- for (i = 0; i < count; i++)
- {
- nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(serverArray, i);
- if (!server) continue;
+ nsCOMPtr<nsIArray> folderArray;
+ rv = accountManager->GetAllFolders(getter_AddRefs(folderArray));
+ NS_ENSURE_SUCCESS(rv, rv);
- // get the base directory
- nsCOMPtr<nsILocalFile> serverPath;
- rv = server->GetLocalPath(getter_AddRefs(serverPath));
- if (NS_FAILED(rv)) continue;
-
- // compare, getting the relative descriptor
- nsCAutoString pathStr;
- aLocalPath->GetRelativeDescriptor(serverPath, pathStr);
+ PRUint32 count;
+ rv = folderArray->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
- // if the argument directory is inside the main server directory
- if (!StringBeginsWith(pathStr, NS_LITERAL_CSTRING("../")))
- {
- nsCString serverURI;
- rv = server->GetServerURI(serverURI);
- if (NS_FAILED(rv)) continue;
+ for (PRUint32 i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryElementAt(folderArray, i, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
- PRInt32 sbdIndex;
- while((sbdIndex = pathStr.Find(".sbd", PR_TRUE)) != -1)
- pathStr.Cut(sbdIndex, 4);
-
- mailboxUri = serverURI;
- mailboxUri.Append('/');
+ nsCOMPtr<nsILocalFile> folderPath;
+ rv = folder->GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
- // If it's a local folder, escape the folder name
- nsCAutoString localStoreType;
- server->GetLocalStoreType(localStoreType);
- if (localStoreType.Equals(NS_LITERAL_CSTRING("mailbox")))
- {
- nsCAutoString escapedPathStr;
- MsgEscapeURL(pathStr, nsINetUtil::ESCAPE_URL_MINIMAL, escapedPathStr);
- mailboxUri.Append(escapedPathStr);
- }
- else
- mailboxUri.Append(pathStr);
+ // Check if we're equal
+ PRBool isEqual;
+ rv = folderPath->Equals(aLocalPath, &isEqual);
+ NS_ENSURE_SUCCESS(rv, rv);
- break;
- }
+ if (isEqual)
+ return folder->GetURI(mailboxUri);
}
- return mailboxUri.IsEmpty() ? NS_ERROR_FAILURE : NS_OK;
+ return NS_ERROR_FAILURE;
}
nsresult MsgGetLocalFileFromURI(const nsACString &aUTF8Path, nsILocalFile **aFile)
{
nsresult rv;
nsCOMPtr<nsIURI> argURI;
rv = NS_NewURI(getter_AddRefs(argURI), aUTF8Path);
NS_ENSURE_SUCCESS(rv, rv);
new file mode 100644
--- /dev/null
+++ b/mailnews/base/util/traceHelper.js
@@ -0,0 +1,146 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+EXPORTED_SYMBOLS = ['DebugTraceHelper'];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var SPACES = " ";
+var BRIGHT_COLORS = {
+ red: "\x1b[1;31m",
+ green: "\x1b[1;32m",
+ yellow: "\x1b[1;33m",
+ blue: "\x1b[1;34m",
+ magenta: "\x1b[1;35m",
+ cyan: "\x1b[1;36m",
+ white: "\x1b[1;37m",
+};
+var DARK_COLORS = {
+ red: "\x1b[0;31m",
+ green: "\x1b[0;32m",
+ yellow: "\x1b[0;33m",
+ blue: "\x1b[0;34m",
+ magenta: "\x1b[0;35m",
+ cyan: "\x1b[0;36m",
+ white: "\x1b[0;37m",
+};
+var STOP_COLORS = "\x1b[0m";
+
+
+/**
+ * Example usages:
+ *
+ * Components.utils.import("resource://app/modules/traceHelper.js");
+ * var debugContext = {color: "cyan"};
+ * DebugTraceHelper.tracify(FolderDisplayWidget.prototype,
+ * "FolderDisplayWidget", /.+/, debugContext);
+ * DebugTraceHelper.tracify(MessageDisplayWidget.prototype,
+ * "MessageDisplayWidget", /.+/, debugContext);
+ * DebugTraceHelper.tracify(StandaloneFolderDisplayWidget.prototype,
+ * "StandaloneFolderDisplayWidget", /.+/, debugContext);
+ * DebugTraceHelper.tracify(StandaloneMessageDisplayWidget.prototype,
+ * "StandaloneMessageDisplayWidget", /.+/, debugContext);
+ * DebugTraceHelper.tracify(DBViewWrapper.prototype,
+ * "DBViewWrapper", /.+/, {color: "green"});
+ * DebugTraceHelper.tracify(JSTreeSelection.prototype,
+ * "JSTreeSelection", /.+/, {color: "yellow"});
+ */
+var DebugTraceHelper = {
+ tracify: function(aObj, aDesc, aPat, aContext, aSettings) {
+ aContext.depth = 0;
+ let color = aSettings.color || "cyan";
+ aSettings.introCode = BRIGHT_COLORS[color];
+ aSettings.outroCode = DARK_COLORS[color];
+ for each (let key in Iterator(aObj, true)) {
+ if (aPat.test(key)) {
+ // ignore properties!
+ if (aObj.__lookupGetter__(key) || aObj.__lookupSetter__(key))
+ continue;
+ // ignore non-functions!
+ if (typeof(aObj[key]) != "function")
+ continue;
+ let name = key;
+ let prev = aObj[name];
+ aObj[name] = function() {
+ let argstr = "";
+ for (let i = 0; i < arguments.length; i++) {
+ let arg = arguments[i];
+ if (arg == null)
+ argstr += " null";
+ else if (typeof(arg) == "function")
+ argstr += " function "+ arg.name;
+ else
+ argstr += " " + arguments[i].toString();
+ }
+
+ let indent = SPACES.substr(0, aContext.depth++ * 2);
+ dump(indent + "--> " + aSettings.introCode + aDesc + "::" + name +
+ ":" + argstr +
+ STOP_COLORS + "\n");
+ let ret;
+ try {
+ ret = prev.apply(this, arguments);
+ }
+ catch (ex) {
+ if (ex.stack) {
+ dump(BRIGHT_COLORS.red + "Exception: " + ex + "\n " +
+ ex.stack.replace("\n", "\n ") + STOP_COLORS + "\n");
+ }
+ else {
+ dump(BRIGHT_COLORS.red + "Exception: " + ex.fileName + ":" +
+ ex.lineNumber + ": " + ex + STOP_COLORS + "\n");
+ }
+ aContext.depth--;
+ dump(indent + "<-- " + aSettings.outroCode + aDesc + "::" + name +
+ STOP_COLORS + "\n");
+ throw ex;
+ }
+ aContext.depth--;
+ dump(indent + "<-- " + aSettings.outroCode + aDesc + "::" + name +
+ ": " + (ret != null ? ret.toString() : "null") +
+ STOP_COLORS + "\n");
+ return ret;
+ };
+ }
+ }
+ }
+};
deleted file mode 100644
--- a/mailnews/compose/public/nsIMsgComposeProgress.idl
+++ /dev/null
@@ -1,69 +0,0 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either of the GNU General Public License Version 2 or later (the "GPL"),
- * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-#include "nsISupports.idl"
-#include "domstubs.idl"
-#include "nsIPrompt.idl"
-#include "nsIWebProgressListener.idl"
-
-interface nsIDOMWindowInternal;
-
-[scriptable, uuid(2080d500-149e-11d5-9daa-e276a42bc27c)]
-interface nsIMsgComposeProgress: nsIWebProgressListener {
-
- /* Open the progress widget, usually it's a dialog
- you can specify the subject of the message,
- and specify if it a save or send operation
- */
- void openProgress(in nsIDOMWindowInternal parent, in wstring subject, in boolean itsASaveOperation);
-
- /* Close the progress widget, usually it's a dialog */
- void closeProgress(in boolean forceClose);
-
- /* Register a Web Progress Listener */
- void registerListener(in nsIWebProgressListener listener);
-
- /* Unregister a Web Progress Listener */
- void unregisterListener(in nsIWebProgressListener listener);
-
- /* Retrieve the prompter, needed to display modal dialog on top of progress dialog */
- nsIPrompt getPrompter();
-
- /* Indicated if the user asked to cancel the current process */
- attribute boolean processCanceledByUser;
-};
-
-
--- a/mailnews/compose/src/nsMsgCompose.cpp
+++ b/mailnews/compose/src/nsMsgCompose.cpp
@@ -889,17 +889,17 @@ nsMsgCompose::Initialize(nsIDOMWindowInt
}
nsresult nsMsgCompose::SetDocumentCharset(const char *charset)
{
// Set charset, this will be used for the MIME charset labeling.
m_compFields->SetCharacterSet(charset);
// notify the change to editor
- m_editor->SetDocumentCharacterSet(nsDependentCString(charset));
+ m_editor->SetDocumentCharacterSet(charset ? nsDependentCString(charset): EmptyCString());
return NS_OK;
}
NS_IMETHODIMP
nsMsgCompose::RegisterStateListener(nsIMsgComposeStateListener *aStateListener)
{
NS_ENSURE_ARG_POINTER(aStateListener);
@@ -1173,18 +1173,16 @@ NS_IMETHODIMP nsMsgCompose::SendMsg(MSG_
return NS_ERROR_FAILURE;
params->SetSubject(msgSubject.get());
params->SetDeliveryMode(deliverMode);
mProgress->OpenProgressDialog(m_window, aMsgWindow,
"chrome://messenger/content/messengercompose/sendProgress.xul",
PR_FALSE, params);
-
- mProgress->GetPrompter(getter_AddRefs(prompt));
}
}
}
mProgress->OnStateChange(nsnull, nsnull, nsIWebProgressListener::STATE_START, NS_OK);
}
PRBool attachVCard = PR_FALSE;
--- a/mailnews/compose/src/nsMsgComposeService.cpp
+++ b/mailnews/compose/src/nsMsgComposeService.cpp
@@ -1079,17 +1079,17 @@ nsMsgTemplateReplyHelper::OnDataAvailabl
char readBuf[1024];
PRUint32 available, readCount;
PRUint32 maxReadCount = sizeof(readBuf) - 1;
rv = inStream->Available(&available);
while (NS_SUCCEEDED(rv) && available > 0)
{
- PRInt32 bodyOffset = 0, readOffset = 0;
+ PRUint32 bodyOffset = 0, readOffset = 0;
if (!mInMsgBody && mLastBlockChars[0])
{
memcpy(readBuf, mLastBlockChars, 3);
readOffset = 3;
maxReadCount -= 3;
}
if (maxReadCount > available)
maxReadCount = available;
@@ -1097,17 +1097,17 @@ nsMsgTemplateReplyHelper::OnDataAvailabl
rv = inStream->Read(readBuf + readOffset, maxReadCount, &readCount);
available -= readCount;
readCount += readOffset;
// we're mainly interested in the msg body, so we need to
// find the header/body delimiter of a blank line. A blank line
// looks like <CR><CR>, <LF><LF>, or <CRLF><CRLF>
if (!mInMsgBody)
{
- for (PRInt32 charIndex = 0; charIndex < readCount && !bodyOffset; charIndex++)
+ for (PRUint32 charIndex = 0; charIndex < readCount && !bodyOffset; charIndex++)
{
if (readBuf[charIndex] == '\r' || readBuf[charIndex] == '\n')
{
if (charIndex + 1 < readCount)
{
if (readBuf[charIndex] == readBuf[charIndex + 1])
{
// got header+body separator
--- a/mailnews/compose/src/nsMsgSend.cpp
+++ b/mailnews/compose/src/nsMsgSend.cpp
@@ -372,23 +372,16 @@ nsMsgComposeAndSend::~nsMsgComposeAndSen
NS_IMETHODIMP nsMsgComposeAndSend::GetDefaultPrompt(nsIPrompt ** aPrompt)
{
NS_ENSURE_ARG(aPrompt);
*aPrompt = nsnull;
nsresult rv = NS_OK;
- if (mSendProgress)
- {
- rv = mSendProgress->GetPrompter(aPrompt);
- if (NS_SUCCEEDED(rv) && *aPrompt)
- return NS_OK;
- }
-
if (mParentWindow)
{
rv = mParentWindow->GetPrompter(aPrompt);
if (NS_SUCCEEDED(rv) && *aPrompt)
return NS_OK;
}
/* If we cannot find a prompter, try the mail3Pane window */
new file mode 100644
--- /dev/null
+++ b/mailnews/compose/test/unit/tail_compose.js
@@ -0,0 +1,1 @@
+load("../../mailnews/resources/mailShutdown.js");
--- a/mailnews/db/gloda/modules/datastore.js
+++ b/mailnews/db/gloda/modules/datastore.js
@@ -2214,17 +2214,16 @@ var GlodaDatastore = {
return this._updateMessagesMarkDeletedByFolderID;
},
markMessagesDeletedByFolderID:
function gloda_ds_markMessagesDeletedByFolderID(aFolderID) {
let statement = this._updateMessagesMarkDeletedByFolderID;
statement.bindInt64Parameter(0, aFolderID);
statement.executeAsync(this.trackAsync());
- statement.finalize();
},
markMessagesDeletedByIDs: function gloda_ds_markMessagesDeletedByIDs(
aMessageIDs) {
let sqlString = "UPDATE messages SET deleted = 1 WHERE id IN (" +
aMessageIDs.join(",") + ")";
let statement = this._createAsyncStatement(sqlString, true);
--- a/mailnews/db/gloda/modules/indexer.js
+++ b/mailnews/db/gloda/modules/indexer.js
@@ -2167,16 +2167,17 @@ var GlodaIndexer = {
* action, but arguably a set of completely duplicate messages is not
* a high priority for indexing.
*/
folderMoveCopyCompleted: function gloda_indexer_folderMoveCopyCompleted(
aMove, aSrcFolder, aDestFolder) {
this.indexer._log.debug("folderMoveCopy notification (Move: " + aMove
+ ")");
if (aMove) {
+ let srcURI = aSrcFolder.URI;
let targetURI = aDestFolder.URI +
srcURI.substring(srcURI.lastIndexOf("/"));
return this._folderRenameHelper(aSrcFolder, targetURI);
}
this.indexer.indexingSweepNeeded = true;
},
/**
@@ -2359,16 +2360,17 @@ var GlodaIndexer = {
onHdrDeleted: function(aHdrChanged, aParentKey, aFlags, aInstigator) {},
onHdrAdded: function(aHdrChanged, aParentKey, aFlags, aInstigator) {},
onParentChanged: function(aKeyChanged, aOldParent, aNewParent,
aInstigator) {},
onReadChanged: function(aInstigator) {},
onJunkScoreChanged: function(aInstigator) {},
onHdrPropertyChanged: function (aHdrToChange, aPreChange, aStatus,
aInstigator) {},
+ onEvent: function (aDB, aEvent) {},
},
_indexMessage: function gloda_indexMessage(aMsgHdr, aCallbackHandle) {
this._log.debug("*** Indexing message: " + aMsgHdr.messageKey + " : " +
aMsgHdr.subject);
MsgHdrToMimeMessage(aMsgHdr, aCallbackHandle.callbackThis,
aCallbackHandle.callback);
let [,aMimeMsg] = yield this.kWorkAsync;
new file mode 100644
--- /dev/null
+++ b/mailnews/db/gloda/test/unit/tail_gloda.js
@@ -0,0 +1,1 @@
+load("../../mailnews/resources/mailShutdown.js");
--- a/mailnews/db/msgdb/public/nsIDBChangeListener.idl
+++ b/mailnews/db/msgdb/public/nsIDBChangeListener.idl
@@ -35,22 +35,23 @@
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
#include "MailNewsTypes2.idl"
interface nsIDBChangeAnnouncer;
interface nsIMsgDBHdr;
+interface nsIMsgDatabase;
/**
* These callbacks are provided to allow listeners to the message database
- * to update their status when message changes occur.
+ * to update their status when changes occur.
*/
-[scriptable, uuid(F2E12285-79D4-4692-AFEF-92415FCC6C5E)]
+[scriptable, uuid(21c56d34-71b9-42bb-9606-331a6a5f8210)]
interface nsIDBChangeListener : nsISupports {
/*
* Callback when message flags are changed
*
* @param aHdrChanged the changed header
* @param aOldFlags message flags prior to change
* @param aNewFlags message flags after change
@@ -79,10 +80,24 @@ interface nsIDBChangeListener : nsISupp
*
* @param aHdrToChange the message header that is changing.
* @param aPreChange true on first call before change, false on second call after change
* @param aStatus storage location provided by calling routine for status
* @param aInstigator object that initiated the change
*/
void onHdrPropertyChanged(in nsIMsgDBHdr aHdrToChange, in PRBool aPreChange, inout PRUint32 aStatus,
in nsIDBChangeListener aInstigator);
+
+ /**
+ * Generic notification for extensibility. Common events should be documented
+ * here so we have a hope of keeping the documentation up to date.
+ * Current events are:
+ * "DBOpened" - When a pending listener becomes real. This can happen when
+ * the existing db is force closed and a new one opened. Only
+ * registered pending listeners are notified.
+ *
+ * @param aDB the db for this event.
+ * @param aEvent type of event.
+ *
+ */
+ void onEvent(in nsIMsgDatabase aDB, in string aEvent);
};
--- a/mailnews/db/msgdb/public/nsIMsgDatabase.idl
+++ b/mailnews/db/msgdb/public/nsIMsgDatabase.idl
@@ -131,17 +131,17 @@ interface nsMsgDBCommitType
[ptr] native nsMsgKeyArrayPtr(nsTArray<nsMsgKey>);
/**
* A service to open mail databases and manipulate listeners automatically.
*
* The contract ID for this component is
* <tt>\@mozilla.org/msgDatabase/msgDBService;1</tt>.
*/
-[scriptable, uuid(26cb7242-a4ba-4811-ad7b-ff993450aae3)]
+[scriptable, uuid(959d0a27-9a00-4ce8-b01d-180ff2387b6a)]
interface nsIMsgDBService : nsISupports
{
/**
* Opens a database for a given folder.
*
* This method is preferred over nsIMsgDBService::openMailDBFromFile if the
* caller has an actual nsIMsgFolder around. If the database detects that it
* is unreadable or out of date (using nsIMsgDatabase::outOfDate) it will
@@ -225,16 +225,25 @@ interface nsIMsgDBService : nsISupports
/**
* Removes the listener from all folder listener sets.
*
* @param aListener The listener to remove.
* @exception NS_ERROR_FAILURE
* The listener is not registered.
*/
void unregisterPendingListener(in nsIDBChangeListener aListener);
+
+ /**
+ * Get the db for a folder, if already open.
+ *
+ * @param aFolder The folder to get the cached (open) db for.
+ *
+ * @returns null if the db isn't open, otherwise the db.
+ */
+ nsIMsgDatabase cachedDBForFolder(in nsIMsgFolder aFolder);
};
[scriptable, uuid(51fd11c9-5be6-4cad-af6b-fb18d6232be4)]
interface nsIMsgDatabase : nsIDBChangeAnnouncer {
/**
* Opens a database folder.
*
* @param aFolderName The name of the folder to create.
--- a/mailnews/db/msgdb/public/nsMsgDatabase.h
+++ b/mailnews/db/msgdb/public/nsMsgDatabase.h
@@ -50,16 +50,17 @@
#include "nsDBFolderInfo.h"
#include "nsICollation.h"
#include "nsIMimeConverter.h"
#include "nsCOMPtr.h"
#include "nsCOMArray.h"
#include "pldhash.h"
#include "nsTArray.h"
#include "nsTObserverArray.h"
+#include "nsAutoPtr.h"
class ListContext;
class nsMsgKeySet;
class nsMsgThread;
class nsIMsgThread;
class nsIDBFolderInfo;
class nsIMsgHeaderParser;
const PRInt32 kMsgDBVersion = 1;
@@ -68,20 +69,54 @@ class nsMsgDBService : public nsIMsgDBSe
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIMSGDBSERVICE
nsMsgDBService();
~nsMsgDBService();
protected:
+ void HookupPendingListeners(nsIMsgDatabase *db, nsIMsgFolder *folder);
+
nsCOMArray <nsIMsgFolder> m_foldersPendingListeners;
nsCOMArray <nsIDBChangeListener> m_pendingListeners;
};
+class nsMsgDBEnumerator : public nsISimpleEnumerator {
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // nsMsgDBEnumerator methods:
+ typedef nsresult (*nsMsgDBEnumeratorFilter)(nsIMsgDBHdr* hdr, void* closure);
+
+ nsMsgDBEnumerator(nsMsgDatabase* db, nsIMdbTable *table,
+ nsMsgDBEnumeratorFilter filter, void* closure,
+ PRBool iterateForwards = PR_TRUE);
+ virtual ~nsMsgDBEnumerator();
+
+ void Clear();
+
+protected:
+ nsresult GetRowCursor();
+ nsresult PrefetchNext();
+ nsRefPtr<nsMsgDatabase> mDB;
+ nsCOMPtr<nsIMdbTableRowCursor> mRowCursor;
+ nsCOMPtr<nsIMsgDBHdr> mResultHdr;
+ PRBool mDone;
+ PRBool mNextPrefetched;
+ PRBool mIterateForwards;
+ nsMsgDBEnumeratorFilter mFilter;
+ nsCOMPtr <nsIMdbTable> mTable;
+ void* mClosure;
+};
+
+
class nsMsgDatabase : public nsIMsgDatabase
{
public:
friend class nsMsgDBService;
friend class nsMsgPropertyEnumerator; // accesses m_mdbEnv and m_mdbStore
NS_DECL_ISUPPORTS
NS_DECL_NSIDBCHANGEANNOUNCER
@@ -182,31 +217,30 @@ protected:
// threading interfaces
virtual nsresult CreateNewThread(nsMsgKey key, const char *subject, nsMsgThread **newThread);
virtual PRBool ThreadBySubjectWithoutRe();
virtual PRBool UseStrictThreading();
virtual PRBool UseCorrectThreading();
virtual nsresult ThreadNewHdr(nsMsgHdr* hdr, PRBool &newThread);
virtual nsresult AddNewThread(nsMsgHdr *msgHdr);
virtual nsresult AddToThread(nsMsgHdr *newHdr, nsIMsgThread *thread, nsIMsgDBHdr *pMsgHdr, PRBool threadInThread);
-
-
+
static nsTArray<nsMsgDatabase*>* m_dbCache;
static nsTArray<nsMsgDatabase*>* GetDBCache();
static void AddToCache(nsMsgDatabase* pMessageDB)
{
#ifdef DEBUG_David_Bienvenu
// NS_ASSERTION(GetDBCache()->Length() < 50, "50 or more open db's");
#endif
GetDBCache()->AppendElement(pMessageDB);
}
static void RemoveFromCache(nsMsgDatabase* pMessageDB);
PRBool MatchDbName(nsILocalFile *dbName); // returns TRUE if they match
-
+
// Flag handling routines
virtual nsresult SetKeyFlag(nsMsgKey key, PRBool set, PRUint32 flag,
nsIDBChangeListener *instigator = NULL);
virtual nsresult SetMsgHdrFlag(nsIMsgDBHdr *msgHdr, PRBool set, PRUint32 flag,
nsIDBChangeListener *instigator);
virtual PRBool SetHdrFlag(nsIMsgDBHdr *, PRBool bSet, nsMsgMessageFlagType flag);
virtual PRBool SetHdrReadFlag(nsIMsgDBHdr *, PRBool pRead);
@@ -327,16 +361,19 @@ protected:
PLDHashTable *m_msgReferences;
nsresult GetRefFromHash(nsCString &reference, nsMsgKey *threadId);
nsresult AddRefToHash(nsCString &reference, nsMsgKey threadId);
nsresult AddMsgRefsToHash(nsIMsgDBHdr *msgHdr);
nsresult RemoveRefFromHash(nsCString &reference);
nsresult RemoveMsgRefsFromHash(nsIMsgDBHdr *msgHdr);
nsresult InitRefHash();
+ // not-reference holding array of enumerators we've handed out.
+ // If a db goes away, it will clean up the outstanding enumerators.
+ nsTArray<nsMsgDBEnumerator *> m_enumerators;
private:
PRUint32 m_cacheSize;
};
class nsMsgRetentionSettings : public nsIMsgRetentionSettings
{
public:
nsMsgRetentionSettings();
--- a/mailnews/db/msgdb/src/nsMsgDatabase.cpp
+++ b/mailnews/db/msgdb/src/nsMsgDatabase.cpp
@@ -173,24 +173,37 @@ NS_IMETHODIMP nsMsgDBService::OpenFolder
{
PRInt32 numMessages;
msgDatabase->m_mdbAllMsgHeadersTable->GetCount(msgDatabase->GetEnv(), &numHdrsInTable);
msgDatabase->m_dbFolderInfo->GetNumMessages(&numMessages);
if (numMessages != (PRInt32) numHdrsInTable)
msgDatabase->SyncCounts();
}
}
-
- for (PRInt32 listenerIndex = 0; listenerIndex < m_foldersPendingListeners.Count(); listenerIndex++)
+ HookupPendingListeners(msgDB, aFolder);
+ return rv;
+}
+
+/**
+ * When a db is opened, we need to hook up any pending listeners for
+ * that db, and notify them.
+ */
+void nsMsgDBService::HookupPendingListeners(nsIMsgDatabase *db,
+ nsIMsgFolder *folder)
+{
+ for (PRInt32 listenerIndex = 0;
+ listenerIndex < m_foldersPendingListeners.Count(); listenerIndex++)
{
// check if we have a pending listener on this db, and if so, add it.
- if (m_foldersPendingListeners[listenerIndex] == aFolder)
- (*_retval)->AddListener(m_pendingListeners.ObjectAt(listenerIndex));
+ if (m_foldersPendingListeners[listenerIndex] == folder)
+ {
+ db->AddListener(m_pendingListeners.ObjectAt(listenerIndex));
+ m_pendingListeners.ObjectAt(listenerIndex)->OnEvent(db, "DBOpened");
+ }
}
- return rv;
}
// This method is called when the caller is trying to create a db without
// having a corresponding nsIMsgFolder object. This happens in a few
// situations, including imap folder discovery, compacting local folders,
// and copying local folders.
NS_IMETHODIMP nsMsgDBService::OpenMailDBFromFile(nsILocalFile *aFolderName, PRBool aCreate, PRBool aLeaveInvalidDB, nsIMsgDatabase** pMessageDB)
{
@@ -239,23 +252,18 @@ NS_IMETHODIMP nsMsgDBService::CreateNewD
rv = msgDB->Open(folderPath, PR_TRUE, PR_TRUE);
NS_ENSURE_TRUE(rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING, rv);
NS_IF_ADDREF(*_retval = msgDB);
nsMsgDatabase *msgDatabase = static_cast<nsMsgDatabase *>(*_retval);
msgDatabase->m_folder = aFolder;
- // Add all pending listeners to the database
- for (PRInt32 listenerIndex = 0;
- listenerIndex < m_foldersPendingListeners.Count(); listenerIndex++)
- {
- if (m_foldersPendingListeners[listenerIndex] == aFolder)
- msgDatabase->AddListener(m_pendingListeners.ObjectAt(listenerIndex));
- }
+ HookupPendingListeners(msgDB, aFolder);
+
return NS_OK;
}
/* void registerPendingListener (in nsIMsgFolder aFolder, in nsIDBChangeListener aListener); */
NS_IMETHODIMP nsMsgDBService::RegisterPendingListener(nsIMsgFolder *aFolder, nsIDBChangeListener *aListener)
{
// need to make sure we don't hold onto these forever. Maybe a shutdown listener?
// if there is a db open on this folder already, we should register the listener.
@@ -281,16 +289,24 @@ NS_IMETHODIMP nsMsgDBService::Unregister
msgDB->RemoveListener(aListener);
m_foldersPendingListeners.RemoveObjectAt(listenerIndex);
m_pendingListeners.RemoveObjectAt(listenerIndex);
return NS_OK;
}
return NS_ERROR_FAILURE;
}
+NS_IMETHODIMP nsMsgDBService::CachedDBForFolder(nsIMsgFolder *aFolder, nsIMsgDatabase **aRetDB)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aRetDB);
+ *aRetDB = nsMsgDatabase::FindInCache(aFolder);
+ return NS_OK;
+}
+
static PRBool gGotGlobalPrefs = PR_FALSE;
static PRBool gThreadWithoutRe = PR_TRUE;
static PRBool gStrictThreading = PR_FALSE;
static PRBool gCorrectThreading = PR_FALSE;
void nsMsgDatabase::GetGlobalPrefs()
{
if (!gGotGlobalPrefs)
@@ -373,16 +389,17 @@ nsresult nsMsgDatabase::AddHdrToCache(ns
{
MsgHdrHashElement* element = reinterpret_cast<MsgHdrHashElement*>(hdr);
if (element && element->mHdr)
{
nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(element->mHdr); // closed system, so this is ok
// clear out m_mdbRow member variable - the db is going away, which means that this member
// variable might very well point to a mork db that is gone.
+ NS_IF_RELEASE(msgHdr->m_mdbRow);
msgHdr->m_mdbRow = nsnull;
}
return PL_DHASH_NEXT;
}
NS_IMETHODIMP nsMsgDatabase::SetMsgHdrCacheSize(PRUint32 aSize)
{
@@ -408,30 +425,37 @@ NS_IMETHODIMP nsMsgDatabase::ClearCached
}
#endif
return NS_OK;
}
void nsMsgDatabase::ClearCachedObjects(PRBool dbGoingAway)
{
ClearHdrCache(PR_FALSE);
-#ifdef DEBUG_bienvenu1
+#ifdef DEBUG_DavidBienvenu
if (m_headersInUse && m_headersInUse->entryCount > 0)
{
NS_ASSERTION(PR_FALSE, "leaking headers");
printf("leaking %d headers in %s\n", m_headersInUse->entryCount, (const char *) m_dbName);
}
#endif
// We should only clear the use hdr cache when the db is going away, or we could
// end up with multiple copies of the same logical msg hdr, which will lead to
// ref-counting problems.
if (dbGoingAway)
ClearUseHdrCache();
m_cachedThread = nsnull;
m_cachedThreadId = nsMsgKey_None;
+ // clean out existing enumerators
+ nsTArray<nsMsgDBEnumerator *> copyEnumerators;
+ copyEnumerators.SwapElements(m_enumerators);
+
+ PRInt32 numEnums = copyEnumerators.Length();
+ for (PRUint32 i = 0; i < numEnums; i++)
+ copyEnumerators[i]->Clear();
}
nsresult nsMsgDatabase::ClearHdrCache(PRBool reInit)
{
if (m_cachedHeaders)
{
// save this away in case we renter this code.
PLDHashTable *saveCachedHeaders = m_cachedHeaders;
@@ -1835,18 +1859,16 @@ PRUint32 nsMsgDatabase::GetStatusFlags(
{
PRUint32 statusFlags = origFlags;
PRBool isRead = PR_TRUE;
nsMsgKey key;
(void)msgHdr->GetMessageKey(&key);
if (!m_newSet.IsEmpty() && m_newSet[m_newSet.Length() - 1] == key || m_newSet.BinaryIndexOf(key) != -1)
statusFlags |= nsMsgMessageFlags::New;
- else
- statusFlags &= ~nsMsgMessageFlags::New;
if (IsHeaderRead(msgHdr, &isRead) == NS_OK && isRead)
statusFlags |= nsMsgMessageFlags::Read;
return statusFlags;
}
nsresult nsMsgDatabase::IsHeaderRead(nsIMsgDBHdr *msgHdr, PRBool *pRead)
{
if (!msgHdr)
@@ -2459,17 +2481,20 @@ NS_IMETHODIMP nsMsgDatabase::ClearNewLis
nsCOMPtr <nsIMsgDBHdr> msgHdr;
err = GetMsgHdrForKey(lastNewKey, getter_AddRefs(msgHdr));
if (NS_SUCCEEDED(err))
{
PRUint32 flags;
(void)msgHdr->GetFlags(&flags);
if ((flags | nsMsgMessageFlags::New) != flags)
+ {
+ msgHdr->AndFlags(~nsMsgMessageFlags::New, &flags);
NotifyHdrChangeAll(msgHdr, flags | nsMsgMessageFlags::New, flags, nsnull);
+ }
}
if (elementIndex == 0)
break;
}
}
return err;
}
@@ -2488,67 +2513,42 @@ NS_IMETHODIMP nsMsgDatabase::GetFirstNew
if (NS_FAILED(rv)) return rv;
*result = (hasnew) ? m_newSet.ElementAt(0) : nsMsgKey_None;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
-
-class nsMsgDBEnumerator : public nsISimpleEnumerator {
-public:
- NS_DECL_ISUPPORTS
-
- // nsISimpleEnumerator methods:
- NS_DECL_NSISIMPLEENUMERATOR
-
- // nsMsgDBEnumerator methods:
- typedef nsresult (*nsMsgDBEnumeratorFilter)(nsIMsgDBHdr* hdr, void* closure);
-
- nsMsgDBEnumerator(nsMsgDatabase* db, nsIMdbTable *table,
- nsMsgDBEnumeratorFilter filter, void* closure,
- PRBool iterateForwards = PR_TRUE);
- virtual ~nsMsgDBEnumerator();
-
-protected:
- nsresult GetRowCursor();
- nsresult PrefetchNext();
- nsMsgDatabase* mDB;
- nsIMdbTableRowCursor* mRowCursor;
- nsIMsgDBHdr* mResultHdr;
- PRBool mDone;
- PRBool mNextPrefetched;
- PRBool mIterateForwards;
- nsMsgDBEnumeratorFilter mFilter;
- nsCOMPtr <nsIMdbTable> mTable;
- void* mClosure;
-
-};
-
nsMsgDBEnumerator::nsMsgDBEnumerator(nsMsgDatabase* db,
nsIMdbTable *table,
nsMsgDBEnumeratorFilter filter,
void* closure,
PRBool iterateForwards)
- : mDB(db), mRowCursor(nsnull), mResultHdr(nsnull), mDone(PR_FALSE),
- mFilter(filter), mClosure(closure), mIterateForwards(iterateForwards)
-{
- NS_ADDREF(mDB);
- mNextPrefetched = PR_FALSE;
- mTable = table;
+ : mDB(db), mDone(PR_FALSE), mFilter(filter),
+ mClosure(closure), mIterateForwards(iterateForwards)
+{
+ mNextPrefetched = PR_FALSE;
+ mTable = table;
+ mDB->m_enumerators.AppendElement(this);
}
nsMsgDBEnumerator::~nsMsgDBEnumerator()
{
- if (mRowCursor)
- mRowCursor->Release();
+ Clear();
+}
+
+void nsMsgDBEnumerator::Clear()
+{
+ mRowCursor = nsnull;
mTable = nsnull;
- NS_IF_RELEASE(mResultHdr);
- NS_RELEASE(mDB);
+ mResultHdr = nsnull;
+ if (mDB)
+ mDB->m_enumerators.RemoveElement(this);
+ mDB = nsnull;
}
NS_IMPL_ISUPPORTS1(nsMsgDBEnumerator, nsISimpleEnumerator)
nsresult nsMsgDBEnumerator::GetRowCursor()
{
mDone = PR_FALSE;
@@ -2561,32 +2561,32 @@ nsresult nsMsgDBEnumerator::GetRowCursor
startPos = -1;
}
else
{
mdb_count numRows;
mTable->GetCount(mDB->GetEnv(), &numRows);
startPos = numRows; // startPos is 0 relative.
}
- return mTable->GetTableRowCursor(mDB->GetEnv(), startPos, &mRowCursor);
+ return mTable->GetTableRowCursor(mDB->GetEnv(), startPos, getter_AddRefs(mRowCursor));
}
NS_IMETHODIMP nsMsgDBEnumerator::GetNext(nsISupports **aItem)
{
if (!aItem)
return NS_ERROR_NULL_POINTER;
nsresult rv = NS_OK;
if (!mNextPrefetched)
rv = PrefetchNext();
if (NS_SUCCEEDED(rv))
{
if (mResultHdr)
{
*aItem = mResultHdr;
- NS_ADDREF(mResultHdr);
+ NS_ADDREF(*aItem);
mNextPrefetched = PR_FALSE;
}
}
return rv;
}
nsresult nsMsgDBEnumerator::PrefetchNext()
{
@@ -2599,17 +2599,16 @@ nsresult nsMsgDBEnumerator::PrefetchNext
{
rv = GetRowCursor();
if (NS_FAILED(rv))
return rv;
}
do
{
- NS_IF_RELEASE(mResultHdr);
mResultHdr = nsnull;
if (mIterateForwards)
rv = mRowCursor->NextRow(mDB->GetEnv(), &hdrRow, &rowPos);
else
rv = mRowCursor->PrevRow(mDB->GetEnv(), &hdrRow, &rowPos);
if (!hdrRow)
{
mDone = PR_TRUE;
@@ -2621,21 +2620,21 @@ nsresult nsMsgDBEnumerator::PrefetchNext
return rv;
}
//Get key from row
mdbOid outOid;
nsMsgKey key=0;
if (hdrRow->GetOid(mDB->GetEnv(), &outOid) == NS_OK)
key = outOid.mOid_Id;
- rv = mDB->GetHdrFromUseCache(key, &mResultHdr);
+ rv = mDB->GetHdrFromUseCache(key, getter_AddRefs(mResultHdr));
if (NS_SUCCEEDED(rv) && mResultHdr)
hdrRow->Release();
else
- rv = mDB->CreateMsgHdr(hdrRow, key, &mResultHdr);
+ rv = mDB->CreateMsgHdr(hdrRow, key, getter_AddRefs(mResultHdr));
if (NS_FAILED(rv))
return rv;
if (mResultHdr)
mResultHdr->GetFlags(&flags);
else
flags = 0;
}
@@ -2649,18 +2648,18 @@ nsresult nsMsgDBEnumerator::PrefetchNext
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP nsMsgDBEnumerator::HasMoreElements(PRBool *aResult)
{
if (!aResult)
return NS_ERROR_NULL_POINTER;
- if (!mNextPrefetched)
- PrefetchNext();
+ if (!mNextPrefetched && (NS_FAILED(PrefetchNext())))
+ mDone = PR_TRUE;
*aResult = !mDone;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
NS_IMETHODIMP
nsMsgDatabase::EnumerateMessages(nsISimpleEnumerator* *result)
@@ -2841,16 +2840,21 @@ NS_IMETHODIMP nsMsgDBThreadEnumerator::O
{
mTableCursor = nsnull;
NS_IF_RELEASE(mResultThread);
mDB->RemoveListener(this);
mDB = nsnull;
return NS_OK;
}
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnEvent(nsIMsgDatabase *aDB, const char *aEvent)
+{
+ return NS_OK;
+}
+
/* void onReadChanged (in nsIDBChangeListener aInstigator); */
NS_IMETHODIMP nsMsgDBThreadEnumerator::OnReadChanged(nsIDBChangeListener *aInstigator)
{
return NS_OK;
}
/* void onJunkScoreChanged (in nsIDBChangeListener aInstigator); */
NS_IMETHODIMP nsMsgDBThreadEnumerator::OnJunkScoreChanged(nsIDBChangeListener *aInstigator)
@@ -3820,16 +3824,17 @@ nsresult nsMsgDatabase::CreateNewThread(
#endif
threadRow->CutAllColumns(GetEnv());
nsCOMPtr<nsIMdbRow> metaRow;
threadTable->GetMetaRow(GetEnv(), nsnull, nsnull, getter_AddRefs(metaRow));
if (metaRow)
metaRow->CutAllColumns(GetEnv());
CharPtrToRowCellColumn(threadRow, m_threadSubjectColumnToken, subject);
+ threadRow->Release();
}
*pnewThread = new nsMsgThread(this, threadTable);
if (*pnewThread)
{
(*pnewThread)->SetThreadKey(threadId);
m_cachedThread = *pnewThread;
--- a/mailnews/db/msgdb/src/nsMsgThread.cpp
+++ b/mailnews/db/msgdb/src/nsMsgThread.cpp
@@ -42,17 +42,16 @@
#include "nsCOMPtr.h"
#include "MailNewsTypes2.h"
NS_IMPL_ISUPPORTS1(nsMsgThread, nsIMsgThread)
nsMsgThread::nsMsgThread()
{
-
MOZ_COUNT_CTOR(nsMsgThread);
Init();
}
nsMsgThread::nsMsgThread(nsMsgDatabase *db, nsIMdbTable *table)
{
MOZ_COUNT_CTOR(nsMsgThread);
Init();
m_mdbTable = table;
@@ -82,20 +81,20 @@ void nsMsgThread::Init()
}
nsMsgThread::~nsMsgThread()
{
MOZ_COUNT_DTOR(nsMsgThread);
if (m_mdbTable)
m_mdbTable->Release();
+ if (m_metaRow)
+ m_metaRow->Release();
if (m_mdbDB)
m_mdbDB->Release();
- if (m_metaRow)
- m_metaRow->Release();
}
nsresult nsMsgThread::InitCachedValues()
{
nsresult err = NS_OK;
NS_ENSURE_TRUE(m_mdbDB && m_metaRow, NS_ERROR_INVALID_POINTER);
new file mode 100644
--- /dev/null
+++ b/mailnews/db/msgdb/test/unit/tail_msgdb.js
@@ -0,0 +1,1 @@
+load("../../mailnews/resources/mailShutdown.js");
new file mode 100644
--- /dev/null
+++ b/mailnews/db/msgdb/test/unit/test_enumerator_cleanup.js
@@ -0,0 +1,47 @@
+/*
+ * Test nsMsgDatabase's cleanup of nsMsgDBEnumerators
+ */
+
+const copyService = Cc["@mozilla.org/messenger/messagecopyservice;1"]
+ .getService(Ci.nsIMsgCopyService);
+const anyOldMessage = do_get_file("../../mailnews/data/bugmail1");
+
+/**
+ * Test closing a db with an outstanding enumerator.
+ */
+function test_enumerator_cleanup() {
+ let db = gLocalInboxFolder.msgDatabase;
+ let enumerator = db.EnumerateMessages();
+ db.forceFolderDBClosed(gLocalInboxFolder);
+ gLocalInboxFolder.msgDatabase = null;
+ db = null;
+ gc();
+ while (enumerator.hasMoreElements())
+ var header = enumerator.getNext();
+
+ do_test_finished();
+}
+
+/*
+ * This infrastructure down here exists just to get
+ * test_references_header_parsing its message header.
+ */
+
+function run_test() {
+ loadLocalMailAccount();
+ do_test_pending();
+ copyService.CopyFileMessage(anyOldMessage, gLocalInboxFolder, null, false, 0,
+ "", messageHeaderGetterListener, null);
+ return true;
+}
+
+var messageHeaderGetterListener = {
+ OnStartCopy: function() {},
+ OnProgress: function(aProgress, aProgressMax) {},
+ GetMessageId: function (aMessageId) {},
+ SetMessageKey: function(aKey) {
+ },
+ OnStopCopy: function(aStatus) {
+ do_timeout(0, "test_enumerator_cleanup();");
+ }
+}
--- a/mailnews/db/msgdb/test/unit/test_propertyEnumerator.js
+++ b/mailnews/db/msgdb/test/unit/test_propertyEnumerator.js
@@ -88,10 +88,11 @@ function continue_test()
//dump("\nProperty 2 is " + property);
properties.push(property);
}
do_check_true(properties.indexOf("flags") >= 0);
do_check_true(properties.indexOf("size") >= 0);
do_check_true(properties.indexOf("iamnew") >= 0);
do_check_true(properties.indexOf("idonotexist") < 0);
+ gHdr = null;
do_test_finished();
}
new file mode 100644
--- /dev/null
+++ b/mailnews/extensions/bayesian-spam-filter/test/unit/tail_bayesian.js
@@ -0,0 +1,1 @@
+load("../../mailnews/resources/mailShutdown.js");
--- a/mailnews/extensions/newsblog/content/Feed.js
+++ b/mailnews/extensions/newsblog/content/Feed.js
@@ -42,31 +42,36 @@ const kNewsBlogInvalidFeed = 1; // usual
const kNewsBlogRequestFailure = 2; // generic networking failure when trying to download the feed.
const kNewsBlogFeedIsBusy = 3;
const kNewsBlogNoNewItems = 4; // there are no new articles for this feed
// Cache for all of the feeds currently being downloaded, indexed by URL, so the load event listener
// can access the Feed objects after it finishes downloading the feed.
var FeedCache =
{
- mFeeds: new Array(),
+ mFeeds: {},
putFeed: function (aFeed)
{
this.mFeeds[this.normalizeHost(aFeed.url)] = aFeed;
},
getFeed: function (aUrl)
{
- return this.mFeeds[this.normalizeHost(aUrl)];
+ var index = this.normalizeHost(aUrl);
+ if (index in this.mFeeds)
+ return this.mFeeds[index];
+ return null;
},
removeFeed: function (aUrl)
{
- delete this.mFeeds[this.normalizeHost(aUrl)];
+ var index = this.normalizeHost(aUrl);
+ if (index in this.mFeeds)
+ delete this.mFeeds[index];
},
normalizeHost: function (aUrl)
{
var ioService = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
var normalizedUrl = ioService.newURI(aUrl, null, null);
normalizedUrl.host = normalizedUrl.host.toLowerCase();
--- a/mailnews/extensions/newsblog/content/FeedItem.js
+++ b/mailnews/extensions/newsblog/content/FeedItem.js
@@ -45,21 +45,18 @@ const ENCLOSURE_BOUNDARY_PREFIX = "-----
const ENCLOSURE_HEADER_BOUNDARY_PREFIX = "------------"; // 12 dashes
const MESSAGE_TEMPLATE = "\n\
<html>\n\
<head>\n\
<title>%TITLE%</title>\n\
<base href=\"%BASE%\">\n\
</head>\n\
- <body id=\"msgBody\" selected=\"false\">\n\
- <div id=\"msgSummary\">\n\
- %CONTENT%\n\
- </div>\n\
- <iframe id=\"msgIframe\" selected=\"false\" src=\"\">\n\
+ <body id=\"msgFeedSummaryBody\" selected=\"false\">\n\
+ %CONTENT%\n\
</body>\n\
</html>\n\
";
function FeedItem()
{
this.mDate = new Date().toString();
this.mUnicodeConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
@@ -392,17 +389,17 @@ FeedItem.prototype =
source += 'Content-Type: text/html; charset=' + this.characterSet + '\n' +
'\n' + this.content;
}
debug(this.identity + " is " + source.length + " characters long");
// Get the folder and database storing the feed's messages and headers.
- folder = this.feed.folder.QueryInterface(Components.interfaces.nsIMsgLocalMailFolder);
+ var folder = this.feed.folder.QueryInterface(Components.interfaces.nsIMsgLocalMailFolder);
var msgFolder = folder.QueryInterface(Components.interfaces.nsIMsgFolder);
msgFolder.gettingNewMessages = true;
// Source is a unicode string, we want to save a char * string in
// the original charset. So convert back
folder.addMessage(this.mUnicodeConverter.ConvertFromUnicode(source));
msgFolder.gettingNewMessages = false;
this.markStored();
}
--- a/mailnews/extensions/newsblog/js/newsblog.js
+++ b/mailnews/extensions/newsblog/js/newsblog.js
@@ -390,17 +390,17 @@ function loadScripts()
// this number may not reflect the # of entries in our mFeeds array because not all
// feeds may have reported in for the first time...
var gNumPendingFeedDownloads = 0;
var progressNotifier = {
mSubscribeMode: false,
mMsgWindow: null,
mStatusFeedback: null,
- mFeeds: new Array,
+ mFeeds: {},
init: function(aMsgWindow, aSubscribeMode)
{
if (!gNumPendingFeedDownloads) // if we aren't already in the middle of downloading feed items...
{
this.mStatusFeedback = aMsgWindow ? aMsgWindow.statusFeedback : null;
this.mSubscribeMode = aSubscribeMode;
this.mMsgWindow = aMsgWindow;
@@ -440,21 +440,19 @@ var progressNotifier = {
this.mStatusFeedback.showStatusString(
newsBlogBundle.formatStringFromName("newsblog-feedNotValid", [feed.url], 1));
else if (aErrorCode == kNewsBlogRequestFailure)
this.mStatusFeedback.showStatusString(newsBlogBundle.formatStringFromName("newsblog-networkError",
[feed.url], 1));
this.mStatusFeedback.stopMeteors();
}
- gNumPendingFeedDownloads--;
-
- if (!gNumPendingFeedDownloads)
+ if (!--gNumPendingFeedDownloads)
{
- this.mFeeds = new Array;
+ this.mFeeds = {};
this.mSubscribeMode = false;
// should we do this on a timer so the text sticks around for a little while?
// It doesnt look like we do it on a timer for newsgroups so we'll follow that model.
if (aErrorCode == kNewsBlogSuccess && this.mStatusFeedback) // don't clear the status text if we just dumped an error to the status bar!
this.mStatusFeedback.showStatusString("");
}
--- a/mailnews/imap/public/nsIImapMailFolderSink.idl
+++ b/mailnews/imap/public/nsIImapMailFolderSink.idl
@@ -59,17 +59,17 @@ interface ImapOnlineCopyStateType
const long kFailedDelete = 4;
const long kReadyForAppendData = 5;
const long kFailedAppend = 6;
const long kInterruptedState = 7;
const long kFailedCopy = 8;
const long kFailedMove = 9;
};
-[scriptable, uuid(471adbb6-95c2-4a5f-b98e-0055804fce48)]
+[scriptable, uuid(531d4a3a-4921-4b7d-a46d-c66be8bec781)]
interface nsIImapMailFolderSink : nsISupports {
attribute boolean folderNeedsACLListed;
attribute boolean folderNeedsSubscribing;
attribute boolean folderNeedsAdded;
attribute unsigned long aclFlags;
attribute long uidValidity;
/**
* Whether we have asked the server for this folder's quota information.
@@ -108,17 +108,17 @@ interface nsIImapMailFolderSink : nsISup
void closeMockChannel(in nsIImapMockChannel aChannel);
void setUrlState(in nsIImapProtocol aProtocol, in nsIMsgMailNewsUrl aUrl, in boolean isRunning, in nsresult status);
void releaseUrlCacheEntry(in nsIMsgMailNewsUrl aUrl);
void headerFetchCompleted(in nsIImapProtocol aProtocol);
void setBiffStateAndUpdate(in long biffState);
void progressStatus(in nsIImapProtocol aProtocol, in unsigned long aMsgId, in wstring extraInfo);
void percentProgress(in nsIImapProtocol aProtocol, in wstring aMessage,
- in long aCurrentProgress, in long aMaxProgressProgressInfo);
+ in long long aCurrentProgress, in long long aMaxProgressProgressInfo);
void clearFolderRights();
void setCopyResponseUid(in string msgIdString,
in nsIImapUrl aUrl);
void setAppendMsgUid(in nsMsgKey newKey,
in nsIImapUrl aUrl);
ACString getMessageId(in nsIImapUrl aUrl);
};
--- a/mailnews/imap/public/nsIMsgImapMailFolder.idl
+++ b/mailnews/imap/public/nsIMsgImapMailFolder.idl
@@ -101,17 +101,17 @@ interface nsIMsgImapMailFolder : nsISupp
void fillInFolderProps(in nsIMsgImapFolderProps aFolderProps);
void resetNamespaceReferences();
void folderPrivileges(in nsIMsgWindow aWindow);
nsIMsgImapMailFolder findOnlineSubFolder(in ACString onlineName);
void addFolderRights(in ACString userName, in ACString rights);
void refreshFolderRights();
void updateStatus(in nsIUrlListener aListener, in nsIMsgWindow aMsgWindow);
- void updateFolder(in nsIMsgWindow aWindow, in nsIUrlListener aListener);
+ void updateFolderWithListener(in nsIMsgWindow aMsgWindow, in nsIUrlListener aListener);
// this is used to issue an arbitrary imap command on the passed in msgs.
// It assumes the command needs to be run in the selected state.
nsIURI issueCommandOnMsgs(in ACString command, in string uids, in nsIMsgWindow aWindow);
nsIURI fetchCustomMsgAttribute(in ACString msgAttribute, in string uids, in nsIMsgWindow aWindow);
nsIURI storeCustomKeywords(in nsIMsgWindow aMsgWindow,
in ACString aFlagsToAdd,
in ACString aFlagsToSubtract,
[array, size_is (aNumKeys)] in nsMsgKey aKeysToStore,
--- a/mailnews/imap/src/nsAutoSyncState.cpp
+++ b/mailnews/imap/src/nsAutoSyncState.cpp
@@ -471,17 +471,17 @@ NS_IMETHODIMP nsAutoSyncState::OnStopRun
nsCString folderName;
ownerFolder->GetURI(folderName);
printf("folder %s status changed serverNextUID = %lx lastNextUID = %lx\n", folderName.get(),
serverNextUID, mLastNextUID);
printf("serverTotal = %lx lastServerTotal = %lx serverRecent = %lx lastServerRecent = %lx\n",
serverTotal, mLastServerTotal, serverRecent, mLastServerRecent);
#endif
mSyncState = nsAutoSyncState::stUpdateIssued;
- return imapFolder->UpdateFolder(nsnull, autoSyncMgrListener);
+ return imapFolder->UpdateFolderWithListener(nsnull, autoSyncMgrListener);
}
else
{
ownerFolder->SetMsgDatabase(nsnull);
// nothing more to do.
mSyncState = nsAutoSyncState::stCompletedIdle;
// autoSyncMgr needs this notification, so manufacture it.
return autoSyncMgrListener->OnStopRunningUrl(nsnull, NS_OK);
--- a/mailnews/imap/src/nsImapMailFolder.cpp
+++ b/mailnews/imap/src/nsImapMailFolder.cpp
@@ -679,31 +679,31 @@ nsresult nsImapMailFolder::GetDatabase()
mDatabase = database;
}
}
return rv;
}
NS_IMETHODIMP nsImapMailFolder::UpdateFolder(nsIMsgWindow * inMsgWindow)
{
- return UpdateFolder(inMsgWindow, nsnull);
-}
-
-NS_IMETHODIMP nsImapMailFolder::UpdateFolder(nsIMsgWindow *msgWindow, nsIUrlListener *aUrlListener)
+ return UpdateFolderWithListener(inMsgWindow, nsnull);
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateFolderWithListener(nsIMsgWindow *aMsgWindow, nsIUrlListener *aUrlListener)
{
nsresult rv;
PRBool selectFolder = PR_FALSE;
if (mFlags & nsMsgFolderFlags::Inbox)
{
if (!m_filterList)
- rv = GetFilterList(msgWindow, getter_AddRefs(m_filterList));
+ rv = GetFilterList(aMsgWindow, getter_AddRefs(m_filterList));
// if there's no msg window, but someone is updating the inbox, we're
// doing something biff-like, and may download headers, so make biff notify.
- if (!msgWindow)
+ if (!aMsgWindow)
SetPerformingBiff(PR_TRUE);
}
if (m_filterList)
{
nsCOMPtr<nsIMsgIncomingServer> server;
rv = GetServer(getter_AddRefs(server));
NS_ENSURE_SUCCESS(rv, rv);
@@ -737,30 +737,30 @@ NS_IMETHODIMP nsImapMailFolder::UpdateFo
}
m_haveDiscoveredAllFolders = PR_TRUE;
}
selectFolder = PR_FALSE;
}
rv = GetDatabase();
if (NS_FAILED(rv))
{
- ThrowAlertMsg("errorGettingDB", msgWindow);
+ ThrowAlertMsg("errorGettingDB", aMsgWindow);
return rv;
}
PRBool canOpenThisFolder = PR_TRUE;
GetCanOpenFolder(&canOpenThisFolder);
PRBool hasOfflineEvents = PR_FALSE;
GetFlag(nsMsgFolderFlags::OfflineEvents, &hasOfflineEvents);
if (!WeAreOffline())
{
if (hasOfflineEvents)
{
- nsImapOfflineSync *goOnline = new nsImapOfflineSync(msgWindow, this, this);
+ nsImapOfflineSync *goOnline = new nsImapOfflineSync(aMsgWindow, this, this);
if (goOnline)
return goOnline->ProcessNextOperation();
}
}
// Check it we're password protecting the local store.
if (!PromptForMasterPasswordIfNecessary())
return NS_ERROR_FAILURE;
@@ -769,34 +769,34 @@ NS_IMETHODIMP nsImapMailFolder::UpdateFo
selectFolder = PR_FALSE;
// don't run select if we can't select the folder...
if (NS_SUCCEEDED(rv) && !m_urlRunning && selectFolder)
{
nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr <nsIURI> url;
- rv = imapService->SelectFolder(m_thread, this, m_urlListener, msgWindow, getter_AddRefs(url));
+ rv = imapService->SelectFolder(m_thread, this, m_urlListener, aMsgWindow, getter_AddRefs(url));
if (NS_SUCCEEDED(rv))
{
m_urlRunning = PR_TRUE;
m_updatingFolder = PR_TRUE;
}
if (url)
{
nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(url, &rv);
NS_ENSURE_SUCCESS(rv, rv);
mailnewsUrl->RegisterListener(this);
m_urlListener = aUrlListener;
}
switch (rv)
{
case NS_MSG_ERROR_OFFLINE:
- if (msgWindow)
- AutoCompact(msgWindow);
+ if (aMsgWindow)
+ AutoCompact(aMsgWindow);
// note fall through to next case.
case NS_BINDING_ABORTED:
rv = NS_OK;
NotifyFolderEvent(mFolderLoadedAtom);
break;
default:
break;
}
@@ -2718,17 +2718,18 @@ NS_IMETHODIMP nsImapMailFolder::UpdateIm
PRInt32 previousUnreadMessages = (m_numServerUnseenMessages)
? m_numServerUnseenMessages : GetNumPendingUnread() + mNumUnreadMessages;
if (numUnread != previousUnreadMessages || m_nextUID != prevNextUID)
{
PRInt32 unreadDelta = numUnread - (GetNumPendingUnread() + mNumUnreadMessages);
if (numUnread - previousUnreadMessages != unreadDelta)
NS_WARNING("unread count should match server count");
ChangeNumPendingUnread(unreadDelta);
- if (unreadDelta > 0)
+ if (unreadDelta > 0 &&
+ !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk)))
{
SetHasNewMessages(PR_TRUE);
SetNumNewMessages(unreadDelta);
SetBiffState(nsMsgBiffState_NewMail);
}
summaryChanged = PR_TRUE;
}
SetPerformingBiff(PR_FALSE);
@@ -4898,16 +4899,34 @@ nsImapMailFolder::OnStopRunningUrl(nsIUR
(void) OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
}
}
else
//clear the copyState if copy has failed
(void) OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
}
break;
+ case nsIImapUrl::nsImapMoveFolderHierarchy:
+ if (m_copyState) // delete folder gets here, but w/o an m_copyState
+ {
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(m_copyState->m_srcSupport);
+ if (srcFolder)
+ {
+ nsCOMPtr<nsIMsgFolder> destFolder;
+ nsString srcName;
+ srcFolder->GetName(srcName);
+ GetChildNamed(srcName, getter_AddRefs(destFolder));
+ if (destFolder)
+ copyService->NotifyCompletion(m_copyState->m_srcSupport, destFolder, aExitCode);
+ }
+ m_copyState = nsnull;
+ }
+ break;
case nsIImapUrl::nsImapRenameFolder:
if (NS_FAILED(aExitCode))
{
nsCOMPtr <nsIAtom> folderRenameAtom;
folderRenameAtom = do_GetAtom("RenameCompleted");
NotifyFolderEvent(folderRenameAtom);
}
break;
@@ -6059,17 +6078,17 @@ nsImapMailFolder::ProgressStatus(nsIImap
}
}
return NS_OK;
}
NS_IMETHODIMP
nsImapMailFolder::PercentProgress(nsIImapProtocol* aProtocol,
const PRUnichar * aMessage,
- PRInt32 aCurrentProgress, PRInt32 aMaxProgress)
+ PRInt64 aCurrentProgress, PRInt64 aMaxProgress)
{
if (aProtocol)
{
nsCOMPtr <nsIImapUrl> imapUrl;
aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
if (imapUrl)
{
nsCOMPtr<nsIImapMockChannel> mockChannel;
@@ -6077,20 +6096,19 @@ nsImapMailFolder::PercentProgress(nsIIma
if (mockChannel)
{
nsCOMPtr<nsIProgressEventSink> progressSink;
mockChannel->GetProgressEventSink(getter_AddRefs(progressSink));
if (progressSink)
{
nsCOMPtr<nsIRequest> request = do_QueryInterface(mockChannel);
if (!request) return NS_ERROR_FAILURE;
- // XXX handle 64-bit ints for real
progressSink->OnProgress(request, nsnull,
- nsUint64(aCurrentProgress),
- nsUint64(aMaxProgress));
+ aCurrentProgress,
+ aMaxProgress);
if (aMessage)
progressSink->OnStatus(request, nsnull, NS_OK, aMessage); // XXX i18n message
}
}
}
}
return NS_OK;
}
@@ -6699,17 +6717,17 @@ void nsImapMailFolder::SetPendingAttribu
dontPreserveEx.AppendLiteral(" ");
// these properties are set as integers below, so don't set them again
// in the iteration through the properties
dontPreserveEx.AppendLiteral("offlineMsgSize msgOffset flags priority ");
// these fields are either copied separately when the server does not support
// custom IMAP flags, or managed directly through the flags
- dontPreserveEx.AppendLiteral("keywords label junkscore ");
+ dontPreserveEx.AppendLiteral("keywords label ");
PRUint32 i, count;
rv = messages->GetLength(&count);
if (NS_FAILED(rv))
return;
// check if any msg hdr has special flags or properties set
@@ -6717,20 +6735,16 @@ void nsImapMailFolder::SetPendingAttribu
for (i = 0; i < count; i++)
{
nsCOMPtr <nsIMsgDBHdr> msgDBHdr = do_QueryElementAt(messages, i, &rv);
if (mDatabase && msgDBHdr)
{
if (!(supportedUserFlags & kImapMsgSupportUserFlag))
{
nsMsgLabelValue label;
- nsCString junkScore;
- msgDBHdr->GetStringProperty("junkscore", getter_Copies(junkScore));
- if (!junkScore.IsEmpty()) // ignore already scored messages.
- mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "junkscore", junkScore.get());
msgDBHdr->GetLabel(&label);
if (label != 0)
{
nsCAutoString labelStr;
labelStr.AppendInt(label);
mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "label", labelStr.get());
}
nsCString keywords;
@@ -7240,35 +7254,41 @@ nsImapMailFolder::CopyFolder(nsIMsgFolde
if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more)
parentPathFile->Remove(PR_TRUE);
}
}
else // non-virtual folder
{
nsCOMPtr <nsIImapService> imapService = do_GetService (NS_IMAPSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
- nsCOMPtr <nsIUrlListener> urlListener = do_QueryInterface(srcFolder);
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
PRBool match = PR_FALSE;
PRBool confirmed = PR_FALSE;
if (mFlags & nsMsgFolderFlags::Trash)
{
rv = srcFolder->MatchOrChangeFilterDestination(nsnull, PR_FALSE, &match);
if (match)
{
srcFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirmed);
// should we return an error to copy service?
// or send a notification?
if (!confirmed)
return NS_OK;
}
}
+ rv = InitCopyState(srcSupport, nsnull, PR_FALSE, nsnull,
+ PR_FALSE, 0, EmptyCString(), listener,
+ msgWindow, PR_FALSE);
+ if (NS_FAILED(rv))
+ return OnCopyCompleted(srcSupport, rv);
+
rv = imapService->MoveFolder(m_thread,
srcFolder,
this,
- urlListener,
+ this,
msgWindow,
nsnull);
}
}
else // copying folder (should only be across server?)
{
nsImapFolderCopyState *folderCopier = new nsImapFolderCopyState(this, srcFolder, isMoveFolder, msgWindow, listener);
NS_ADDREF(folderCopier); // it owns itself.
@@ -7301,20 +7321,31 @@ nsImapMailFolder::CopyFileMessage(nsIFil
return OnCopyCompleted(srcSupport, rv);
rv = QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
if (msgToReplace)
{
rv = msgToReplace->GetMessageKey(&key);
if (NS_SUCCEEDED(rv))
- messageId.AppendInt((PRInt32) key);
- }
-
- rv = InitCopyState(srcSupport, messages, PR_FALSE, isDraftOrTemplate,
+ {
+ messageId.AppendInt((PRInt32) key);
+ // Perhaps we have the message offline, but even if we do it is
+ // not valid, since the only time we do a file copy for an
+ // existing message is when we are changing the message.
+ // So set the offline size to 0 to force SetPendingAttributes to
+ // clear the offline message flag.
+ msgToReplace->SetOfflineMessageSize(0);
+ messages->AppendElement(msgToReplace, PR_FALSE);
+ SetPendingAttributes(messages, PR_FALSE);
+ }
+ }
+
+ PRBool isMove = (msgToReplace ? PR_TRUE : PR_FALSE);
+ rv = InitCopyState(srcSupport, messages, isMove, isDraftOrTemplate,
PR_FALSE, aNewMsgFlags, aNewMsgKeywords, listener,
msgWindow, PR_FALSE);
if (NS_FAILED(rv))
return OnCopyCompleted(srcSupport, rv);
m_copyState->m_streamCopy = PR_TRUE;
nsCOMPtr<nsISupports> copySupport;
if( m_copyState )
@@ -7440,30 +7471,30 @@ nsImapMailFolder::InitCopyState(nsISuppo
PRBool acrossServers,
PRUint32 newMsgFlags,
const nsACString &newMsgKeywords,
nsIMsgCopyServiceListener* listener,
nsIMsgWindow *msgWindow,
PRBool allowUndo)
{
NS_ENSURE_ARG_POINTER(srcSupport);
- NS_ENSURE_ARG_POINTER(messages);
NS_ENSURE_TRUE(!m_copyState, NS_ERROR_FAILURE);
nsresult rv;
nsImapMailCopyState* copyState = new nsImapMailCopyState();
m_copyState = do_QueryInterface(copyState);
NS_ENSURE_TRUE(m_copyState,NS_ERROR_OUT_OF_MEMORY);
m_copyState->m_isCrossServerOp = acrossServers;
m_copyState->m_srcSupport = do_QueryInterface(srcSupport, &rv);
NS_ENSURE_SUCCESS(rv, rv);
m_copyState->m_messages = messages;
- rv = messages->GetLength(&m_copyState->m_totalCount);
+ if (messages)
+ rv = messages->GetLength(&m_copyState->m_totalCount);
if (!m_copyState->m_isCrossServerOp)
{
if (NS_SUCCEEDED(rv))
{
PRUint32 numUnread = 0;
for (PRUint32 keyIndex=0; keyIndex < m_copyState->m_totalCount; keyIndex++)
{
nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(m_copyState->m_messages, keyIndex, &rv);
--- a/mailnews/imap/src/nsImapProtocol.cpp
+++ b/mailnews/imap/src/nsImapProtocol.cpp
@@ -4979,19 +4979,18 @@ nsImapProtocol::ProgressEventFunctionUsi
nsString unicodeStr;
nsresult rv = CopyMUTF7toUTF16(nsDependentCString(aExtraInfo), unicodeStr);
if (NS_SUCCEEDED(rv))
m_imapMailFolderSink->ProgressStatus(this, aMsgId, unicodeStr.get());
}
}
void
-nsImapProtocol::PercentProgressUpdateEvent(PRUnichar *message, PRInt32 currentProgress, PRInt32 maxProgress)
-{
-
+nsImapProtocol::PercentProgressUpdateEvent(PRUnichar *message, PRInt64 currentProgress, PRInt64 maxProgress)
+{
PRInt64 nowMS = LL_ZERO;
PRInt32 percent = (100 * currentProgress) / maxProgress;
if (percent == m_lastPercent)
return; // hasn't changed, right? So just return. Do we need to clear this anywhere?
if (percent < 100) // always need to do 100%
{
int64 minIntervalBetweenProgress;
@@ -5003,23 +5002,25 @@ nsImapProtocol::PercentProgressUpdateEve
LL_SUB(diffSinceLastProgress, diffSinceLastProgress, minIntervalBetweenProgress); // r = a - b
if (!LL_GE_ZERO(diffSinceLastProgress))
return;
}
m_lastPercent = percent;
m_lastProgressTime = nowMS;
- // set our max progress as the content length on the mock channel
- if (m_mockChannel)
- m_mockChannel->SetContentLength(maxProgress);
-
+ // set our max progress on the running URL
+ if (m_runningUrl)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(m_runningUrl));
+ mailnewsUrl->SetMaxProgress(maxProgress);
+ }
if (m_imapMailFolderSink)
- m_imapMailFolderSink->PercentProgress(this, message, currentProgress, maxProgress);
+ m_imapMailFolderSink->PercentProgress(this, message, currentProgress, maxProgress);
}
// imap commands issued by the parser
void
nsImapProtocol::Store(const nsCString &messageList, const char * messageData,
PRBool idsAreUid)
{
@@ -5683,17 +5684,17 @@ void nsImapProtocol::UploadMessageFromFi
PRTime date,
imapMessageFlagsType flags,
nsCString &keywords)
{
if (!file || !mailboxName) return;
IncrementCommandTagNumber();
PRInt64 fileSize = 0;
- PRInt32 totalSize;
+ PRInt64 totalSize;
PRUint32 readCount;
char *dataBuffer = nsnull;
nsCString command(GetServerCommandTag());
nsCString escapedName;
CreateEscapedMailboxName(mailboxName, escapedName);
nsresult rv;
PRBool eof = PR_FALSE;
nsCString flagString;
@@ -5772,16 +5773,19 @@ void nsImapProtocol::UploadMessageFromFi
if (!hasLiteralPlus)
ParseIMAPandCheckForNewMail();
totalSize = fileSize;
readCount = 0;
while(NS_SUCCEEDED(rv) && !eof && totalSize > 0)
{
rv = fileInputStream->Read(dataBuffer, COPY_BUFFER_SIZE, &readCount);
+ if (NS_SUCCEEDED(rv) && !readCount)
+ rv = NS_ERROR_FAILURE;
+
if (NS_SUCCEEDED(rv))
{
NS_ASSERTION(readCount <= (PRUint32) totalSize, "got more bytes than there should be");
dataBuffer[readCount] = 0;
rv = SendData(dataBuffer);
totalSize -= readCount;
PercentProgressUpdateEvent(nsnull, fileSize - totalSize, fileSize);
}
@@ -7206,16 +7210,34 @@ void nsImapProtocol::CreateMailbox(const
nsCString command(GetServerCommandTag());
command += " create \"";
command += escapedName;
command += "\""CRLF;
nsresult rv = SendData(command.get());
if(NS_SUCCEEDED(rv))
ParseIMAPandCheckForNewMail();
+ // If that failed, let's list the parent folder to see if
+ // it allows inferiors, so we won't try to create sub-folders
+ // of the parent folder again in the current session.
+ if (GetServerStateParser().CommandFailed())
+ {
+ // Figure out parent folder name.
+ nsCString parentName(mailboxName);
+ char hierarchyDelimiter;
+ m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
+ PRInt32 leafPos = parentName.RFindChar(hierarchyDelimiter);
+ if (leafPos > 0)
+ {
+ parentName.Truncate(leafPos);
+ List(parentName.get(), PR_FALSE);
+ // We still want the caller to know the create failed, so restore that.
+ GetServerStateParser().SetCommandFailed(PR_TRUE);
+ }
+ }
}
void nsImapProtocol::DeleteMailbox(const char *mailboxName)
{
// check if this connection currently has the folder to be deleted selected.
// If so, we should close it because at least some UW servers don't like you deleting
// a folder you have open.
@@ -8245,17 +8267,16 @@ NS_INTERFACE_MAP_END_THREADSAFE
nsImapMockChannel::nsImapMockChannel()
{
m_channelContext = nsnull;
m_cancelStatus = NS_OK;
mLoadFlags = 0;
mChannelClosed = PR_FALSE;
mReadingFromCache = PR_FALSE;
mTryingToReadPart = PR_FALSE;
- mContentLength = -1;
}
nsImapMockChannel::~nsImapMockChannel()
{
// if we're offline, we may not get to close the channel correctly.
// we need to do this to send the url state change notification in
// the case of mem and disk cache reads.
NS_WARN_IF_FALSE(NS_IsMainThread(), "should only access mock channel on ui thread");
@@ -8411,16 +8432,39 @@ NS_IMETHODIMP nsImapMockChannel::SetURI(
// if we don't have a progress event sink yet, get it from the url for now...
nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
if (mailnewsUrl && !mProgressEventSink)
{
nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
mailnewsUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
mProgressEventSink = do_QueryInterface(statusFeedback);
}
+ // If this is a fetch URL and we can, get the message size from the message
+ // header and set it to be the content length.
+ // Note that for an attachment URL, this will set the content length to be
+ // equal to the size of the entire message.
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url));
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+ if (imapAction == nsIImapUrl::nsImapMsgFetch)
+ {
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
+ if (msgUrl)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ // A failure to get a message header isn't an error
+ msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ PRUint32 messageSize;
+ if (NS_SUCCEEDED(msgHdr->GetMessageSize(&messageSize)))
+ SetContentLength(messageSize);
+ }
+ }
+ }
}
return NS_OK;
}
NS_IMETHODIMP nsImapMockChannel::Open(nsIInputStream **_retval)
{
return NS_ImplementChannelOpen(this, _retval);
}
--- a/mailnews/imap/src/nsImapProtocol.h
+++ b/mailnews/imap/src/nsImapProtocol.h
@@ -261,17 +261,17 @@ public:
void DiscoverMailboxSpec(nsImapMailboxSpec * adoptedBoxSpec);
void AlertUserEventUsingId(PRUint32 aMessageId);
void AlertUserEvent(const char * message);
void AlertUserEventFromServer(const char * aServerEvent);
void ProgressEventFunctionUsingId(PRUint32 aMsgId);
void ProgressEventFunctionUsingIdWithString(PRUint32 aMsgId, const char *
aExtraInfo);
- void PercentProgressUpdateEvent(PRUnichar *message, PRInt32 currentProgress, PRInt32 maxProgress);
+ void PercentProgressUpdateEvent(PRUnichar *message, PRInt64 currentProgress, PRInt64 maxProgress);
void ShowProgress();
// utility function calls made by the server
void Copy(const char * messageList, const char *destinationMailbox,
PRBool idsAreUid);
void Search(const char * searchCriteria, PRBool useUID,
PRBool notifyHit = PR_TRUE);
--- a/mailnews/imap/src/nsImapServerResponseParser.cpp
+++ b/mailnews/imap/src/nsImapServerResponseParser.cpp
@@ -134,16 +134,21 @@ PRBool nsImapServerResponseParser::GetNe
return rv;
}
PRBool nsImapServerResponseParser::CommandFailed()
{
return fCurrentCommandFailed;
}
+void nsImapServerResponseParser::SetCommandFailed(PRBool failed)
+{
+ fCurrentCommandFailed = failed;
+}
+
void nsImapServerResponseParser::SetFlagState(nsIImapFlagAndUidState *state)
{
fFlagState = state;
}
PRInt32 nsImapServerResponseParser::SizeOfMostRecentMessage()
{
return fSizeOfMostRecentMessage;
--- a/mailnews/imap/src/nsImapServerResponseParser.h
+++ b/mailnews/imap/src/nsImapServerResponseParser.h
@@ -69,16 +69,17 @@ public:
// aignoreBadAndNOResponses --> don't throw a error dialog if this command results in a NO or Bad response
// from the server..in other words the command is "exploratory" and we don't really care if it succeeds or fails.
// This value is typically FALSE for almost all cases.
virtual void ParseIMAPServerResponse(const char *aCurrentCommand,
PRBool aIgnoreBadAndNOResponses,
char *aGreetingWithCapability = NULL);
virtual void InitializeState();
PRBool CommandFailed();
+ void SetCommandFailed(PRBool failed);
enum eIMAPstate {
kNonAuthenticated,
kAuthenticated,
kFolderSelected
} ;
virtual eIMAPstate GetIMAPstate();
--- a/mailnews/imap/test/unit/head_server.js
+++ b/mailnews/imap/test/unit/head_server.js
@@ -25,17 +25,17 @@ function makeServer(daemon, infoString)
server.start(IMAP_PORT);
return server;
}
function createLocalIMAPServer() {
var acctmgr = Cc["@mozilla.org/messenger/account-manager;1"]
.getService(Ci.nsIMsgAccountManager);
- var server = acctmgr.createIncomingServer(null, "localhost", "imap");
+ var server = acctmgr.createIncomingServer("user", "localhost", "imap");
server.port = IMAP_PORT;
server.username = "user";
server.password = "password";
server.valid = false;
var account = acctmgr.createAccount();
account.incomingServer = server;
server.valid = true;
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/test/unit/tail_imap.js
@@ -0,0 +1,1 @@
+load("../../mailnews/resources/mailShutdown.js");
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/test/unit/test_imapContentLength.js
@@ -0,0 +1,127 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test content length for the IMAP protocol. This focuses on necko URLs
+ * that are run externally.
+ */
+
+// Take a multipart message as we're testing attachment URLs as well
+const gFile = do_get_file("../../mailnews/data/multipart-complex2");
+var gIMAPDaemon, gIMAPServer, gIMAPIncomingServer, gIMAPInbox;
+const gMFNService = Cc["@mozilla.org/messenger/msgnotificationservice;1"]
+ .getService(Ci.nsIMsgFolderNotificationService);
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessageToServer(file, mailbox)
+{
+ let ioService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+ let URI = ioService.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ mailbox.addMessage(new imapMessage(URI.spec, mailbox.uidnext++, []));
+
+ gIMAPInbox.updateFolder(null);
+}
+
+var msgFolderListener =
+{
+ msgAdded: function(aMsgHdr)
+ {
+ do_timeout_function(0, verifyContentLength, null, [aMsgHdr]);
+ }
+};
+
+
+function run_test()
+{
+ // Disable new mail notifications
+ let prefSvc = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+
+ prefSvc.setBoolPref("mail.biff.play_sound", false);
+ prefSvc.setBoolPref("mail.biff.show_alert", false);
+ prefSvc.setBoolPref("mail.biff.show_tray_icon", false);
+ prefSvc.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ // Set up nsIMsgFolderListener to get the header when it's received
+ gMFNService.addListener(msgFolderListener, gMFNService.msgAdded);
+
+ // set up IMAP fakeserver and incoming server
+ gIMAPDaemon = new imapDaemon();
+ gIMAPServer = makeServer(gIMAPDaemon, "");
+ gIMAPIncomingServer = createLocalIMAPServer();
+
+ // we need a local account for the IMAP server to have its sent messages in
+ loadLocalMailAccount();
+
+ // We need an identity so that updateFolder doesn't fail
+ let acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
+ .getService(Ci.nsIMsgAccountManager);
+ let imapAccount = acctMgr.createAccount();
+ let identity = acctMgr.createIdentity();
+ imapAccount.addIdentity(identity);
+ imapAccount.defaultIdentity = identity;
+ imapAccount.incomingServer = gIMAPIncomingServer;
+ acctMgr.defaultAccount = imapAccount;
+
+ // The server doesn't support more than one connection
+ prefSvc.setIntPref("mail.server.server1.max_cached_connections", 1);
+ // We aren't interested in downloading messages automatically
+ prefSvc.setBoolPref("mail.server.server1.download_on_biff", false);
+
+ gIMAPInbox = gIMAPIncomingServer.rootFolder.getChildNamed("Inbox");
+ gIMAPInbox.flags &= ~Ci.nsMsgFolderFlags.Offline;
+
+ do_test_pending();
+
+ // Add a message to the IMAP server
+ addMessageToServer(gFile, gIMAPDaemon.getMailbox("INBOX"));
+
+ gIMAPInbox.updateFolder(null);
+}
+
+function verifyContentLength(aMsgHdr)
+{
+ let messageUri = gIMAPInbox.getUriForMsg(aMsgHdr);
+ // Convert this to a URI that necko can run
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+ let neckoURL = {};
+ let messageService = messenger.messageServiceFromURI(messageUri);
+ messageService.GetUrlForUri(messageUri, neckoURL, null);
+ // Don't use the necko URL directly. Instead, get the spec and create a new
+ // URL using the IO service
+ let urlToRun = gIOService.newURI(neckoURL.value.spec, null, null);
+
+ // Get a channel from this URI, and check its content length
+ let channel = gIOService.newChannelFromURI(urlToRun);
+ do_check_eq(channel.contentLength, gFile.fileSize);
+
+ // Now try an attachment. &part=1.2
+ let attachmentURL = gIOService.newURI(neckoURL.value.spec + "&part=1.2",
+ null, null);
+ let attachmentChannel = gIOService.newChannelFromURI(attachmentURL);
+ // Currently attachments have their content length set to the length of the
+ // entire message
+ do_check_eq(channel.contentLength, gFile.fileSize);
+
+ do_timeout_function(1000, endTest);
+}
+
+function endTest()
+{
+ gIMAPServer.resetTest();
+ gIMAPIncomingServer.closeCachedConnections();
+ gIMAPServer.performTest();
+ gIMAPServer.stop();
+ let thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents())
+ thread.processNextEvent(true);
+
+ do_test_finished(); // for the one in run_test()
+}
--- a/mailnews/imap/test/unit/test_imapFolderCopy.js
+++ b/mailnews/imap/test/unit/test_imapFolderCopy.js
@@ -47,16 +47,44 @@ const gTestArray =
let folder2 = gIMAPInbox.getChildNamed("empty 2");
dump("found folder2\n");
let folder3 = gIMAPInbox.getChildNamed("empty 3");
dump("found folder3\n");
do_check_neq(folder1, null);
do_check_neq(folder2, null);
do_check_neq(folder3, null);
doTest(++gCurTestNum);
+ },
+ function moveImapFolder1() {
+ let folders = new Array;
+ let folder1 = gIMAPInbox.getChildNamed("empty 1");
+ let folder2 = gIMAPInbox.getChildNamed("empty 2");
+ folders.push(folder2.QueryInterface(Ci.nsIMsgFolder));
+ let array = toXPCOMArray(folders, Ci.nsIMutableArray);
+ gCopyService.CopyFolders(array, folder1, true, CopyListener, null);
+ },
+ function moveImapFolder2() {
+ let folders = new Array;
+ let folder1 = gIMAPInbox.getChildNamed("empty 1");
+ let folder3 = gIMAPInbox.getChildNamed("empty 3");
+ folders.push(folder3.QueryInterface(Ci.nsIMsgFolder));
+ let array = toXPCOMArray(folders, Ci.nsIMutableArray);
+ gCopyService.CopyFolders(array, folder1, true, CopyListener, null);
+ },
+ function verifyImapFolders() {
+ let folder1 = gIMAPInbox.getChildNamed("empty 1");
+ dump("found folder1\n");
+ let folder2 = folder1.getChildNamed("empty 2");
+ dump("found folder2\n");
+ let folder3 = folder1.getChildNamed("empty 3");
+ dump("found folder3\n");
+ do_check_neq(folder1, null);
+ do_check_neq(folder2, null);
+ do_check_neq(folder3, null);
+ doTest(++gCurTestNum);
}
];
function run_test()
{
// This is before any of the actual tests, so...
gTest = 0;
--- a/mailnews/imap/test/unit/test_nsIMsgFolderListenerIMAP.js
+++ b/mailnews/imap/test/unit/test_nsIMsgFolderListenerIMAP.js
@@ -226,18 +226,18 @@ function run_test()
prefBranch.setBoolPref("mail.server.server1.download_on_biff", false);
// Get the server list...
gIMAPIncomingServer.performExpand(null);
// We get these notifications on initial discovery
gRootFolder = gIMAPIncomingServer.rootFolder;
gIMAPInbox = gRootFolder.getChildNamed("Inbox");
- gExpectedEvents = [[gMFNService.folderAdded, gRootFolder, "Trash", "gIMAPTrashFolder"],
- [gMFNService.folderDeleted, [gIMAPInbox]]];
+ gExpectedEvents = [[gMFNService.folderAdded, gRootFolder, "Trash",
+ "gIMAPTrashFolder"]];
gCurrStatus |= kStatus.onStopCopyDone | kStatus.functionCallDone;
gServer.performTest("SUBSCRIBE");
// "Master" do_test_pending(), paired with a do_test_finished() at the end of
// all the operations.
do_test_pending();
}
new file mode 100644
--- /dev/null
+++ b/mailnews/import/test/unit/tail_import.js
@@ -0,0 +1,1 @@
+load("../../mailnews/resources/mailShutdown.js");
--- a/mailnews/jar.mn
+++ b/mailnews/jar.mn
@@ -83,17 +83,16 @@ messenger.jar:
content/messenger/msgSynchronize.js (base/resources/content/msgSynchronize.js)
content/messenger/folderProps.xul (base/resources/content/folderProps.xul)
content/messenger/folderProps.js (base/resources/content/folderProps.js)
content/messenger/folderWidgets.xml (base/resources/content/folderWidgets.xml)
content/messenger/retention.js (base/resources/content/retention.js)
content/messenger/shareglue.js (base/resources/content/shareglue.js)
content/messenger/newFolderDialog.xul (base/resources/content/newFolderDialog.xul)
content/messenger/newFolderDialog.js (base/resources/content/newFolderDialog.js)
- content/messenger/msgViewNavigation.js (base/resources/content/msgViewNavigation.js)
* content/messenger/msgAccountCentral.xul (base/resources/content/msgAccountCentral.xul)
content/messenger/msgAccountCentral.js (base/resources/content/msgAccountCentral.js)
content/messenger/msgFolderPickerOverlay.js (base/resources/content/msgFolderPickerOverlay.js)
content/messenger/renameFolderDialog.xul (base/resources/content/renameFolderDialog.xul)
content/messenger/renameFolderDialog.js (base/resources/content/renameFolderDialog.js)
content/messenger/virtualFolderProperties.xul (base/resources/content/virtualFolderProperties.xul)
content/messenger/virtualFolderProperties.js (base/resources/content/virtualFolderProperties.js)
content/messenger/virtualFolderListDialog.xul (base/resources/content/virtualFolderListDialog.xul)
--- a/mailnews/local/src/nsLocalMailFolder.cpp
+++ b/mailnews/local/src/nsLocalMailFolder.cpp
@@ -963,17 +963,20 @@ nsresult nsMsgLocalMailFolder::IsChildOf
thisFolder = parentFolder;
}
return rv;
}
NS_IMETHODIMP nsMsgLocalMailFolder::Delete()
{
nsresult rv;
- if(mDatabase)
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgDBService->CachedDBForFolder(this, getter_AddRefs(mDatabase));
+ if (mDatabase)
{
mDatabase->ForceClosed();
mDatabase = nsnull;
}
nsCOMPtr<nsILocalFile> pathFile;
rv = GetFilePath(getter_AddRefs(pathFile));
if (NS_FAILED(rv)) return rv;
@@ -2177,18 +2180,21 @@ nsMsgLocalMailFolder::CopyFileMessage(ns
if (NS_SUCCEEDED(rv))
rv = CopyData(inputStream, (PRInt32) fileSize);
if (NS_SUCCEEDED(rv))
rv = EndCopy(PR_TRUE);
//mDatabase should have been initialized above - if we got msgDb
+ // If we were going to delete, here is where we would do it. But because
+ // existing code already supports doing those deletes, we are just going
+ // to end the copy.
if (NS_SUCCEEDED(rv) && msgToReplace && mDatabase)
- rv = DeleteMessage(msgToReplace, msgWindow, PR_TRUE, PR_TRUE);
+ rv = OnCopyCompleted(fileSupport, PR_TRUE);
if (inputStream)
inputStream->Close();
}
if(NS_FAILED(rv))
(void) OnCopyCompleted(fileSupport, PR_FALSE);
@@ -2525,18 +2531,17 @@ NS_IMETHODIMP nsMsgLocalMailFolder::EndC
mCopyState->m_fileStream->Flush();
else
seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, 0);
}
}
//Copy the header to the new database
if (copySucceeded && mCopyState->m_message)
{
- // CopyMessages() goes here; CopyFileMessage() never gets in here because
- // the mCopyState->m_message will be always null for file message
+ // CopyMessages() goes here, and CopyFileMessages() with metadata to save;
nsCOMPtr<nsIMsgDBHdr> newHdr;
if(!mCopyState->m_parseMsgState)
{
if(mCopyState->m_destDB)
{
rv = mCopyState->m_destDB->CopyHdrFromExistingHdr(mCopyState->m_curDstKey,
mCopyState->m_message, PR_TRUE,
getter_AddRefs(newHdr));
--- a/mailnews/local/src/nsMailboxProtocol.cpp
+++ b/mailnews/local/src/nsMailboxProtocol.cpp
@@ -92,41 +92,16 @@ nsMailboxProtocol::nsMailboxProtocol(nsI
}
nsMailboxProtocol::~nsMailboxProtocol()
{
// free our local state
delete m_lineStreamBuffer;
}
-NS_IMETHODIMP nsMailboxProtocol::GetContentLength(PRInt32 * aContentLength)
-{
- *aContentLength = -1;
- if (m_mailboxAction == nsIMailboxUrl::ActionParseMailbox)
- {
- // our file transport knows the entire length of the berkley mail folder
- // so get it from there.
- if (!m_request)
- return NS_OK;
-
- nsCOMPtr<nsIChannel> info = do_QueryInterface(m_request);
- if (info) info->GetContentLength(aContentLength);
- return NS_OK;
-
- }
- else if (m_runningUrl)
- {
- PRUint32 msgSize = 0;
- m_runningUrl->GetMessageSize(&msgSize);
- *aContentLength = (PRInt32) msgSize;
- }
-
- return NS_OK;
-}
-
nsresult nsMailboxProtocol::OpenMultipleMsgTransport(PRUint32 offset, PRInt32 size)
{
nsresult rv;
nsCOMPtr<nsIStreamTransportService> serv =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
@@ -178,29 +153,45 @@ nsresult nsMailboxProtocol::Initialize(n
// clear stopped flag on msg window, because we care.
nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
if (mailnewsUrl)
{
mailnewsUrl->GetMsgWindow(getter_AddRefs(window));
if (window)
window->SetStopped(PR_FALSE);
}
+
if (m_mailboxAction == nsIMailboxUrl::ActionParseMailbox)
+ {
+ // Set the length of the file equal to the max progress
+ nsCOMPtr<nsIFile> file;
+ GetFileFromURL(aURL, getter_AddRefs(file));
+ if (file)
+ {
+ PRInt64 fileSize = 0;
+ file->GetFileSize(&fileSize);
+ mailnewsUrl->SetMaxProgress(fileSize);
+ }
+
rv = OpenFileSocket(aURL, 0, -1 /* read in all the bytes in the file */);
+ }
else
{
// we need to specify a byte range to read in so we read in JUST the message we want.
rv = SetupMessageExtraction();
if (NS_FAILED(rv)) return rv;
nsMsgKey aMsgKey;
PRUint32 aMsgSize = 0;
rv = m_runningUrl->GetMessageKey(&aMsgKey);
NS_ASSERTION(NS_SUCCEEDED(rv), "oops....i messed something up");
rv = m_runningUrl->GetMessageSize(&aMsgSize);
NS_ASSERTION(NS_SUCCEEDED(rv), "oops....i messed something up");
+ SetContentLength(aMsgSize);
+ mailnewsUrl->SetMaxProgress(aMsgSize);
+
if (RunningMultipleMsgUrl())
{
rv = OpenFileSocketForReuse(aURL, (PRUint32) aMsgKey, aMsgSize);
// if we're running multiple msg url, we clear the event sink because the multiple
// msg urls will handle setting the progress.
mProgressEventSink = nsnull;
}
else
@@ -647,24 +638,24 @@ PRInt32 nsMailboxProtocol::ReadMessageRe
SetFlag(MAILBOX_MSG_PARSE_FIRST_LINE);
}
PR_Free(saveLine);
}
while (line && !pauseForMoreData);
}
SetFlag(MAILBOX_PAUSE_FOR_READ); // wait for more data to become available...
- if (mProgressEventSink)
+ if (mProgressEventSink && m_runningUrl)
{
- PRInt32 contentLength = 0;
- GetContentLength(&contentLength);
- // XXX 64-bit
+ PRInt64 maxProgress;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(m_runningUrl));
+ mailnewsUrl->GetMaxProgress(&maxProgress);
mProgressEventSink->OnProgress(this, m_channelContext,
- nsUint64(mCurrentProgress),
- nsUint64(contentLength));
+ mCurrentProgress,
+ maxProgress);
}
if (NS_FAILED(rv)) return -1;
return 0;
}
--- a/mailnews/local/src/nsMailboxProtocol.h
+++ b/mailnews/local/src/nsMailboxProtocol.h
@@ -90,17 +90,16 @@ public:
virtual nsresult LoadUrl(nsIURI * aURL, nsISupports * aConsumer);
////////////////////////////////////////////////////////////////////////////////////////
// we suppport the nsIStreamListener interface
////////////////////////////////////////////////////////////////////////////////////////
NS_IMETHOD OnStartRequest(nsIRequest *request, nsISupports *ctxt);
NS_IMETHOD OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult aStatus);
- NS_IMETHOD GetContentLength(PRInt32 * aContentLength);
private:
nsCOMPtr<nsIMailboxUrl> m_runningUrl; // the nsIMailboxURL that is currently running
nsMailboxAction m_mailboxAction; // current mailbox action associated with this connnection...
PRInt32 m_originalContentLength; /* the content length at the time of calling graph progress */
// Event sink handles
nsCOMPtr<nsIStreamListener> m_mailboxParser;
@@ -108,17 +107,17 @@ private:
// Local state for the current operation
nsMsgLineStreamBuffer * m_lineStreamBuffer; // used to efficiently extract lines from the incoming data stream
// Generic state information -- What state are we in? What state do we want to go to
// after the next response? What was the last response code? etc.
MailboxStatesEnum m_nextState;
MailboxStatesEnum m_initialState;
- PRInt32 mCurrentProgress;
+ PRInt64 mCurrentProgress;
PRUint32 m_messageID;
// can we just use the base class m_tempMsgFile?
nsCOMPtr<nsIFile> m_tempMessageFile;
nsCOMPtr<nsIOutputStream> m_msgFileOutputStream;
// this is used to hold the source mailbox file open when move/copying
// multiple messages.
--- a/mailnews/local/src/nsParseMailbox.cpp
+++ b/mailnews/local/src/nsParseMailbox.cpp
@@ -265,16 +265,21 @@ nsParseMailMessageState::OnAnnouncerGoin
{
m_mailDB->RemoveListener(this);
m_mailDB = nsnull;
m_newMsgHdr = nsnull;
}
return NS_OK;
}
+NS_IMETHODIMP nsParseMailMessageState::OnEvent(nsIMsgDatabase *aDB, const char *aEvent)
+{
+ return NS_OK;
+}
+
/* void OnReadChanged (in nsIDBChangeListener instigator); */
NS_IMETHODIMP
nsParseMailMessageState::OnReadChanged(nsIDBChangeListener *instigator)
{
return NS_OK;
}
/* void OnJunkScoreChanged (in nsIDBChangeListener instigator); */
@@ -1783,16 +1788,21 @@ void nsParseNewMailState::DoneParsingFol
PRInt32 nsParseNewMailState::PublishMsgHeader(nsIMsgWindow *msgWindow)
{
PRBool moved = PR_FALSE;
FinishHeader();
if (m_newMsgHdr)
{
+ PRUint32 newFlags, oldFlags;
+ m_newMsgHdr->GetFlags(&oldFlags);
+ if (!(oldFlags & nsMsgMessageFlags::Read)) // don't mark read messages as new.
+ m_newMsgHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
+
if (!m_disableFilters)
{
// seems like the code that's writing to disk should do
// the flushing...
// flush the inbox because filters will read from disk
// m_inboxFileStream->Flush();
PRUint32 msgOffset;
(void) m_newMsgHdr->GetMessageOffset(&msgOffset);
@@ -1836,16 +1846,18 @@ PRInt32 nsParseNewMailState::PublishMsgH
}
break;
case nsIMsgIncomingServer::moveDupsToTrash:
{
nsCOMPtr <nsIMsgFolder> trash;
GetTrashFolder(getter_AddRefs(trash));
if (trash)
{
+ PRUint32 newFlags;
+ m_newMsgHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags);
// save off m_newMsgHdr because MoveIncorporatedMessage
// clears it by calling nsParseMailMessageState::Init
nsCOMPtr<nsIMsgDBHdr> msgHdr = m_newMsgHdr;
MoveIncorporatedMessage(m_newMsgHdr, m_mailDB, trash,
nsnull, msgWindow);
if (!m_downloadingToTempFile)
m_mailDB->RemoveHeaderMdbRow(msgHdr);
}
@@ -1865,21 +1877,16 @@ PRInt32 nsParseNewMailState::PublishMsgH
}
ApplyFilters(&moved, msgWindow, msgOffset);
}
if (!moved)
{
if (m_mailDB)
{
- PRUint32 newFlags, oldFlags;
- m_newMsgHdr->GetFlags(&oldFlags);
- if (!(oldFlags & nsMsgMessageFlags::Read)) // don't mark read messages as new.
- m_newMsgHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
-
m_mailDB->AddNewHdrToDB(m_newMsgHdr, PR_TRUE);
nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
if (notifier)
notifier->NotifyMsgAdded(m_newMsgHdr);
}
} // if it was moved by imap filter, m_parseMsgState->m_newMsgHdr == nsnull
m_newMsgHdr = nsnull;
}
@@ -2290,19 +2297,24 @@ nsresult nsParseNewMailState::ApplyForwa
return rv;
}
int nsParseNewMailState::MarkFilteredMessageRead(nsIMsgDBHdr *msgHdr)
{
PRUint32 newFlags;
if (m_mailDB)
+ {
m_mailDB->MarkHdrRead(msgHdr, PR_TRUE, nsnull);
+ }
else
+ {
msgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags);
+ msgHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags);
+ }
return 0;
}
nsresult nsParseNewMailState::EndMsgDownload()
{
if (m_moveCoalescer)
m_moveCoalescer->PlaybackMoves();
new file mode 100644
--- /dev/null
+++ b/mailnews/local/test/unit/tail_local.js
@@ -0,0 +1,1 @@
+load("../../mailnews/resources/mailShutdown.js");
new file mode 100644
--- /dev/null
+++ b/mailnews/local/test/unit/test_mailboxContentLength.js
@@ -0,0 +1,73 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test content length for the mailbox protocol. This focuses on necko URLs
+ * that are run externally.
+ */
+
+// Take a multipart message as we're testing attachment URLs as well
+var gFile = do_get_file("../../mailnews/data/multipart-complex2");
+var gMessageKey;
+
+function run_test()
+{
+ // Set up local folders
+ loadLocalMailAccount();
+
+ // Copy a message into the local folder
+ Cc["@mozilla.org/messenger/messagecopyservice;1"]
+ .getService(Ci.nsIMsgCopyService)
+ .CopyFileMessage(gFile, gLocalInboxFolder, null, false, 0, "",
+ gCopyListener, null);
+
+ do_test_pending();
+}
+
+var gCopyListener =
+{
+ OnStartCopy: function() {},
+ OnProgress: function(aProgress, aProgressMax) {},
+ SetMessageKey: function(aKey) { gMessageKey = aKey; },
+ GetMessageId: function(aMessageId) {},
+ OnStopCopy: function(aStatus)
+ {
+ do_timeout_function(0, verifyContentLength);
+ }
+};
+
+function verifyContentLength()
+{
+ // First get the message URI
+ let msgHdr = gLocalInboxFolder.GetMessageHeader(gMessageKey);
+ let messageUri = gLocalInboxFolder.getUriForMsg(msgHdr);
+ // Convert this to a URI that necko can run
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+ let neckoURL = {};
+ let messageService = messenger.messageServiceFromURI(messageUri);
+ messageService.GetUrlForUri(messageUri, neckoURL, null);
+ // Don't use the necko URL directly. Instead, get the spec and create a new
+ // URL using the IO service
+ let ioService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+ let urlToRun = ioService.newURI(neckoURL.value.spec, null, null);
+
+ // Get a channel from this URI, and check its content length
+ let channel = ioService.newChannelFromURI(urlToRun);
+ do_check_eq(channel.contentLength, gFile.fileSize);
+
+ // Now try an attachment. &part=1.2
+ let attachmentURL = ioService.newURI(neckoURL.value.spec + "&part=1.2",
+ null, null);
+ let attachmentChannel = ioService.newChannelFromURI(attachmentURL);
+ // Currently attachments have their content length set to the length of the
+ // entire message
+ do_check_eq(channel.contentLength, gFile.fileSize);
+
+ do_test_finished();
+}
--- a/mailnews/local/test/unit/test_msgCopy.js
+++ b/mailnews/local/test/unit/test_msgCopy.js
@@ -67,13 +67,14 @@ var copyListener =
SetMessageKey: function(aKey)
{
hdrs.push(gLocalInboxFolder.GetMessageHeader(aKey));
},
SetMessageId: function(aMessageId) {},
OnStopCopy: function(aStatus)
{
var copiedMessage = gLocalInboxFolder.GetMessageHeader(hdrs[0]);
- do_check_eq(copiedMessage.getStringProperty("keywords"), tag1);
+ do_check_eq(copiedMessage.getStringProperty("keywords"), tag1);
+ hdrs = null;
do_test_finished();
}
};
--- a/mailnews/mailnews.js
+++ b/mailnews/mailnews.js
@@ -137,17 +137,16 @@ pref("mail.default_templates", ""); // e
// We will still always regenerate .msf files if the file size changes.
pref("mail.db_timestamp_leeway", 4000);
// check all folders for new mail
pref("mail.check_all_imap_folders_for_new", false);
pref("mail.imap.server_sub_directory", "");
pref("mail.imap.max_cached_connections", 10);
-pref("mail.imap.fetch_by_chunks", true);
pref("mail.imap.chunk_size", 65536);
pref("mail.imap.min_chunk_size_threshold", 98304);
pref("mail.imap.chunk_fast", 2);
pref("mail.imap.chunk_ideal", 4);
pref("mail.imap.chunk_add", 8192);
pref("mail.imap.hide_other_users", false);
pref("mail.imap.hide_unused_namespaces", true);
pref("mail.imap.new_mail_get_headers", true);
--- a/mailnews/mime/cthandlers/vcard/mimevcrd.cpp
+++ b/mailnews/mime/cthandlers/vcard/mimevcrd.cpp
@@ -173,63 +173,16 @@ MimeInlineTextVCard_parse_line (const ch
PR_Free (linestring);
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////
-static PRInt32 INTL_ConvertCharset(const char* from_charset, const char* to_charset,
- const char* inBuffer, const PRInt32 inLength,
- char** outBuffer, PRInt32* outLength)
-{
- nsresult res;
-
- // invalid input
- if (nsnull == from_charset || nsnull == to_charset ||
- '\0' == *from_charset || '\0' == *to_charset || nsnull == inBuffer)
- return -1;
-
- // from to identical
- if (!PL_strcasecmp(from_charset, to_charset))
- return -1;
-
- // us-ascii is a subset of utf-8
- if ((!PL_strcasecmp(from_charset, "us-ascii") && !PL_strcasecmp(to_charset, "UTF-8")) ||
- (!PL_strcasecmp(from_charset, "UTF-8") && !PL_strcasecmp(to_charset, "us-ascii")))
- return -1;
-
-
- nsAutoString outString;
-
- res = ConvertToUnicode(from_charset, inBuffer, outString);
-
- // known bug in 4.x, it mixes Shift_JIS (or EUC-JP) and ISO-2022-JP in vCard fields
- if (NS_ERROR_MODULE_UCONV == NS_ERROR_GET_MODULE(res)) {
- if (!PL_strcasecmp("ISO-2022-JP", from_charset)) {
- res = ConvertToUnicode("Shift_JIS", inBuffer, outString);
- if (NS_ERROR_MODULE_UCONV == NS_ERROR_GET_MODULE(res)) {
- res = ConvertToUnicode("EUC-JP", inBuffer, outString);
- }
- }
- }
-
- if (NS_SUCCEEDED(res)) {
- nsCAutoString result;
- res = ConvertFromUnicode(to_charset, outString, result);
- if (NS_SUCCEEDED(res)) {
- *outLength = result.Length();
- *outBuffer = PL_strdup(result.get());
- }
- }
-
- return NS_SUCCEEDED(res) ? 0 : -1;
-}
-////////////////////////////////////////////////////////////////////////////////
static int
MimeInlineTextVCard_parse_eof (MimeObject *obj, PRBool abort_p)
{
nsCOMPtr<nsIMsgVCardService> vCardService =
do_GetService(MSGVCARDSERVICE_CONTRACT_ID);
if (!vCardService)
return -1;
--- a/mailnews/mime/emitters/src/nsMimeHtmlEmitter.cpp
+++ b/mailnews/mime/emitters/src/nsMimeHtmlEmitter.cpp
@@ -72,42 +72,43 @@
* A helper class to implement nsIUTF8StringEnumerator
*/
class nsMimeStringEnumerator : public nsIUTF8StringEnumerator {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIUTF8STRINGENUMERATOR
+ nsMimeStringEnumerator() : mCurrentIndex(0) {}
+
template<class T>
- nsCString* Append(T value) { return values.AppendElement(value); }
+ nsCString* Append(T value) { return mValues.AppendElement(value); }
protected:
- nsTArray<nsCString> values;
+ nsTArray<nsCString> mValues;
+ PRUint32 mCurrentIndex; // consumers expect first-in first-out enumeration
};
NS_IMPL_ISUPPORTS1(nsMimeStringEnumerator, nsIUTF8StringEnumerator)
NS_IMETHODIMP
nsMimeStringEnumerator::HasMore(PRBool *result)
{
NS_ENSURE_ARG_POINTER(result);
- *result = values.Length() != 0;
+ *result = mCurrentIndex < mValues.Length();
return NS_OK;
}
-NS_IMETHODIMP nsMimeStringEnumerator::GetNext(nsACString& result)
+NS_IMETHODIMP
+nsMimeStringEnumerator::GetNext(nsACString& result)
{
- PRUint32 length = values.Length();
- if (!length)
+ if (mCurrentIndex >= mValues.Length())
return NS_ERROR_UNEXPECTED;
- length--;
- result = values[length];
- values.RemoveElementAt(length);
+ result = mValues[mCurrentIndex++];
return NS_OK;
}
/*
* nsMimeHtmlEmitter definitions....
*/
nsMimeHtmlDisplayEmitter::nsMimeHtmlDisplayEmitter() : nsMimeBaseEmitter()
{
--- a/mailnews/mime/src/mimemoz2.cpp
+++ b/mailnews/mime/src/mimemoz2.cpp
@@ -104,19 +104,16 @@ static NS_DEFINE_CID(kParserCID, NS_PARS
#endif
void ValidateRealName(nsMsgAttachmentData *aAttach, MimeHeaders *aHdrs);
static MimeHeadersState MIME_HeaderType;
static PRBool MIME_WrapLongLines;
static PRBool MIME_VariableWidthPlaintext;
-// For string bundle access routines...
-static nsCOMPtr<nsIStringBundle> stringBundle = nsnull;
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Attachment handling routines
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
MimeObject *mime_get_main_object(MimeObject* obj);
nsresult
ProcessBodyAsAttachment(MimeObject *obj, nsMsgAttachmentData **data)
@@ -1985,38 +1982,30 @@ mimeSetNewURL(nsMIMESession *stream, cha
}
#define MIME_URL "chrome://messenger/locale/mime.properties"
extern "C"
char *
MimeGetStringByID(PRInt32 stringID)
{
- char *tempString = nsnull;
- const char *resultString = "???";
- nsresult res = NS_OK;
-
- if (!stringBundle)
- {
- const char* propertyURL = MIME_URL;
+ char *tempString = nsnull;
+ const char *resultString = "???";
+ const char* propertyURL = MIME_URL;
- nsCOMPtr<nsIStringBundleService> sBundleService =
- do_GetService(NS_STRINGBUNDLE_CONTRACTID, &res);
- if (NS_SUCCEEDED(res) && (nsnull != sBundleService))
- {
- res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(stringBundle));
- }
- }
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ stringBundleService->CreateBundle(MIME_URL, getter_AddRefs(stringBundle));
if (stringBundle)
{
nsString v;
- res = stringBundle->GetStringFromID(stringID, getter_Copies(v));
-
- if (NS_SUCCEEDED(res))
+ if (NS_SUCCEEDED(stringBundle->GetStringFromID(stringID, getter_Copies(v))))
tempString = ToNewUTF8String(v);
}
if (!tempString)
tempString = strdup(resultString);
return tempString;
}
--- a/mailnews/mime/src/mimemult.cpp
+++ b/mailnews/mime/src/mimemult.cpp
@@ -286,30 +286,30 @@ MimeMultipart_parse_line (const char *li
else
{
nsCAutoString header("Content-Type: text/x-moz-deleted; name=\"Deleted: ");
header.Append(fileName);
status = MimeWriteAString(obj, header);
if (status < 0)
return status;
status = MimeWriteAString(obj, NS_LITERAL_CSTRING("\""MSG_LINEBREAK"Content-Transfer-Encoding: 8bit"MSG_LINEBREAK));
- MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Disposition: inline; filename=\"Deleted:"));
+ MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Disposition: inline; filename=\"Deleted: "));
MimeWriteAString(obj, fileName);
MimeWriteAString(obj, NS_LITERAL_CSTRING("\""MSG_LINEBREAK"X-Mozilla-Altered: AttachmentDeleted; date=\""));
}
nsCString result;
char timeBuffer[128];
PRExplodedTime now;
PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
PR_FormatTimeUSEnglish(timeBuffer, sizeof(timeBuffer),
"%a %b %d %H:%M:%S %Y",
&now);
MimeWriteAString(obj, nsDependentCString(timeBuffer));
MimeWriteAString(obj, NS_LITERAL_CSTRING("\""MSG_LINEBREAK));
- MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK"The original MIME headers for this attachment are:"MSG_LINEBREAK));
+ MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK"You deleted an attachment from this message. The original MIME headers for the attachment were:"MSG_LINEBREAK));
MimeHeaders_write_raw_headers(mult->hdrs, obj->options, PR_FALSE);
}
status = ((MimeMultipartClass *) obj->clazz)->create_child(obj);
if (status < 0) return status;
NS_ASSERTION(mult->state != MimeMultipartHeaders,
"mult->state shouldn't be MimeMultipartHeaders");
// Ok, at this point, we need to examine the headers and see if there
new file mode 100644
--- /dev/null
+++ b/mailnews/mime/test/unit/tail_mime.js
@@ -0,0 +1,1 @@
+load("../../mailnews/resources/mailShutdown.js");
--- a/mailnews/news/src/nsNNTPProtocol.cpp
+++ b/mailnews/news/src/nsNNTPProtocol.cpp
@@ -460,17 +460,34 @@ NS_IMETHODIMP nsNNTPProtocol::Initialize
nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
if (mailnewsUrl)
{
if (aMsgWindow)
mailnewsUrl->SetMsgWindow(aMsgWindow);
m_runningURL->GetNewsAction(&m_newsAction);
if (m_newsAction == nsINntpUrl::ActionFetchArticle || m_newsAction == nsINntpUrl::ActionFetchPart
- || m_newsAction == nsINntpUrl::ActionSaveMessageToDisk) {
+ || m_newsAction == nsINntpUrl::ActionSaveMessageToDisk)
+ {
+ // Look for the content length
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_runningURL));
+ if (msgUrl)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ // Note that for attachments, the messageSize is going to be the
+ // size of the entire message
+ PRUint32 messageSize;
+ msgHdr->GetMessageSize(&messageSize);
+ SetContentLength(messageSize);
+ }
+ }
+
PRBool msgIsInLocalCache = PR_FALSE;
mailnewsUrl->GetMsgIsInLocalCache(&msgIsInLocalCache);
if (msgIsInLocalCache || WeAreOffline())
return NS_OK; // probably don't need to do anything else - definitely don't want
// to open the socket.
}
}
}
--- a/mailnews/news/src/nsNntpIncomingServer.cpp
+++ b/mailnews/news/src/nsNntpIncomingServer.cpp
@@ -1900,23 +1900,36 @@ nsNntpIncomingServer::IsSeparator(PRInt3
NS_IMETHODIMP
nsNntpIncomingServer::IsSorted(PRBool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
+#ifdef MOZILLA_1_9_1_BRANCH
nsNntpIncomingServer::CanDrop(PRInt32 index, PRInt32 orientation, PRBool *_retval)
+#else
+nsNntpIncomingServer::CanDrop(PRInt32 index,
+ PRInt32 orientation,
+ nsIDOMDataTransfer *dataTransfer,
+ PRBool *_retval)
+#endif
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
+#ifdef MOZILLA_1_9_1_BRANCH
nsNntpIncomingServer::Drop(PRInt32 row, PRInt32 orientation)
+#else
+nsNntpIncomingServer::Drop(PRInt32 row,
+ PRInt32 orientation,
+ nsIDOMDataTransfer *dataTransfer)
+#endif
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetParentIndex(PRInt32 rowIndex, PRInt32 *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
--- a/mailnews/news/src/nsNntpUrl.cpp
+++ b/mailnews/news/src/nsNntpUrl.cpp
@@ -264,25 +264,24 @@ NS_IMETHODIMP nsNntpUrl::GetMessageHeade
nsresult rv;
nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv,rv);
nsCOMPtr <nsIMsgMessageService> msgService = do_QueryInterface(nntpService, &rv);
NS_ENSURE_SUCCESS(rv,rv);
- if (mOriginalSpec.IsEmpty()) {
- // this can happen when viewing a news://host/message-id url
- return NS_ERROR_FAILURE;
- }
+ nsCAutoString spec(mOriginalSpec);
+ if (spec.IsEmpty())
+ // Handle the case where necko directly runs an internal news:// URL,
+ // one that looks like news://host/message-id?group=mozilla.announce&key=15
+ // Other sorts of URLs -- e.g. news://host/message-id -- will not succeed.
+ GetSpec(spec);
- rv = msgService->MessageURIToMsgHdr(mOriginalSpec.get(), aMsgHdr);
- NS_ENSURE_SUCCESS(rv,rv);
-
- return NS_OK;
+ return msgService->MessageURIToMsgHdr(spec.get(), aMsgHdr);
}
NS_IMETHODIMP nsNntpUrl::IsUrlType(PRUint32 type, PRBool *isType)
{
NS_ENSURE_ARG(isType);
switch(type)
{
--- a/mailnews/news/test/unit/head_server_setup.js
+++ b/mailnews/news/test/unit/head_server_setup.js
@@ -167,16 +167,17 @@ function resetFolder(folder) {
while (headerEnum.hasMoreElements())
headers.push(headerEnum.getNext().QueryInterface(Ci.nsIMsgDBHdr));
var db = folder.msgDatabase;
db.dBFolderInfo.knownArtsSet = "";
for each (var header in headers) {
db.DeleteHeader(header, null, true, false);
}
+ dump("resetting folder\n");
folder.msgDatabase = null;
}
function do_check_transaction(real, expected) {
// real.them may have an extra QUIT on the end, where the stream is only
// closed after we have a chance to process it and not them. We therefore
// excise this from the list
if (real.them[real.them.length-1] == "QUIT")
new file mode 100644
--- /dev/null
+++ b/mailnews/news/test/unit/tail_news.js
@@ -0,0 +1,1 @@
+load("../../mailnews/resources/mailShutdown.js");
new file mode 100644
--- /dev/null
+++ b/mailnews/news/test/unit/test_nntpContentLength.js
@@ -0,0 +1,74 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test content length for the news protocol. This focuses on necko URLs
+ * that are run externally.
+ */
+
+// The basic daemon to use for testing nntpd.js implementations
+var daemon = setupNNTPDaemon();
+
+var server;
+var localserver;
+
+function run_test() {
+ // XXX The server doesn't support returning sizes!
+ return;
+
+ type = "RFC 977";
+ var handler = new NNTP_RFC977_handler(daemon);
+ localserver = setupLocalServer(NNTP_PORT);
+ server = new nsMailServer(handler);
+ server.start(NNTP_PORT);
+
+ try {
+ // Get the folder and new mail
+ let folder = localserver.rootFolder.getChildNamed("test.subscribe.simple");
+ folder.clearFlag(Ci.nsMsgFolderFlags.Offline);
+ folder.getNewMessages(null, {
+ OnStopRunningUrl: function () { localserver.closeCachedConnections(); }});
+ server.performTest();
+
+ do_check_eq(folder.getTotalMessages(false), 1);
+ do_check_true(folder.hasNewMessages);
+
+ server.resetTest();
+
+ // Get the message URI
+ let msgHdr = folder.firstNewMessage;
+ let messageUri = folder.getUriForMsg(msgHdr);
+ // Convert this to a URI that necko can run
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+ let neckoURL = {};
+ let messageService = messenger.messageServiceFromURI(messageUri);
+ messageService.GetUrlForUri(messageUri, neckoURL, null);
+ // Don't use the necko URL directly. Instead, get the spec and create a new
+ // URL using the IO service
+ let ioService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+ let urlToRun = ioService.newURI(neckoURL.value.spec, null, null);
+
+ // Get a channel from this URI, and check its content length
+ let channel = ioService.newChannelFromURI(urlToRun);
+ do_check_eq(channel.contentLength, kSimpleNewsArticle.length);
+
+ // Now try an attachment. &part=1.2
+ // XXX the message doesn't really have an attachment
+ let attachmentURL = ioService.newURI(neckoURL.value.spec + "&part=1.2",
+ null, null);
+ let attachmentChannel = ioService.newChannelFromURI(attachmentURL);
+ // Currently attachments have their content length set to the length of the
+ // entire message
+ do_check_eq(channel.contentLength, kSimpleNewsArticle.length);
+ }
+ catch (e) {
+ server.stop();
+ do_throw(e);
+ }
+};
--- a/mailnews/test/data/multipart-complex2
+++ b/mailnews/test/data/multipart-complex2
@@ -1,9 +1,11 @@
+From - Mon Jun 02 19:00:00 2008
Content-Type: multipart/mixed; boundary="bou"
+Message-Id: <123456@example.com>
Part 1
--bou
Content-Type: multipart/related; boundary="bound"
Part 2
--bound
Content-Type: multipart/digest; boundary="boundar"
--- a/mailnews/test/fakeserver/imapd.js
+++ b/mailnews/test/fakeserver/imapd.js
@@ -132,19 +132,21 @@ imapDaemon.prototype = {
box = box.getChild(component);
// Yes, we won't autocreate intermediary boxes
if (box == null || box.flags.indexOf('\\Noinferiors') != -1)
return false;
}
// If this is an imapMailbox...
if (oldBox && oldBox._children) {
// Only delete now so we don't screw ourselves up if creation fails
- this._deleteMailbox(oldBox);
- mailbox._parent = box == this.root ? null : box;
- box.addMailbox(oldBox);
+ this.deleteMailbox(oldBox);
+ oldBox._parent = box == this.root ? null : box;
+ let newBox = new imapMailbox(oldBox.name, box, this.uidvalidity++);
+ newBox._messages = oldBox._messages;
+ box.addMailbox(newBox);
// And if oldBox is an INBOX, we need to recreate that
if (oldBox.name == "INBOX") {
this.inbox = new imapMailbox("INBOX", null, this.uidvalidity++);
this.root.addMailbox(this.inbox);
}
oldBox.name = subName;
} else if (oldBox) {
@@ -252,34 +254,37 @@ imapMailbox.prototype = {
for each (var part in parts) {
index = name.indexOf(part, index);
if (index == -1)
return false;
}
return true;
});
}
- if (matching[0] == this)
- matching.shift();
return matching;
},
get fullName () {
return (this._parent ? this._parent.fullName + this.delimiter : "") +
this.name;
},
get displayName() {
var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "x-imap4-modified-utf7";
return converter.ConvertFromUnicode(this.fullName.replace(
/([\\"])/g, '\\$1')) + converter.Finish();
},
get allChildren() {
return this._children.reduce(function (arr, elem) {
- return arr.concat(elem.allChildren);
+ return arr.concat(elem._allChildrenInternal);
+ }, []);
+ },
+ get _allChildrenInternal() {
+ return this._children.reduce(function (arr, elem) {
+ return arr.concat(elem._allChildrenInternal);
}, [this]);
},
addMessage : function (message) {
this._messages.push(message);
if (message.uid >= this.uidnext)
this.uidnext = message.uid + 1;
if (this._updates.indexOf("EXISTS") == -1)
this._updates.push("EXISTS");
@@ -950,27 +955,26 @@ IMAP_RFC3501_handler.prototype = {
if (!mbox || mbox.name == "")
return "NO no such mailbox";
if (!this._daemon.createMailbox(args[1], mbox))
return "NO cannot rename mailbox";
return "OK RENAME completed";
},
SUBSCRIBE : function (args) {
var mailbox = this._daemon.getMailbox(args[0]);
- if (!mailbox || mailbox.subscribed)
+ if (!mailbox)
return "NO error in subscribing";
mailbox.subscribed = true;
return "OK SUBSCRIBE completed";
},
UNSUBSCRIBE : function (args) {
var mailbox = this._daemon.getMailbox(args[0]);
- if (!mailbox || !mailbox.subscribed)
- return "NO error in unsubscribing";
- mailbox.subscribed = false;
- return "OK SUBSCRIBE completed";
+ if (mailbox)
+ mailbox.subscribed = false;
+ return "OK UNSUBSCRIBE completed";
},
LIST : function (args) {
var base = this._daemon.getMailbox(args[0]);
if (!base)
return "NO no such mailbox";
var people = base.matchKids(args[1]);
var response = "";
for each (var box in people)
@@ -1018,17 +1022,17 @@ IMAP_RFC3501_handler.prototype = {
return count + (message.flags.indexOf('\\Seen') == -1 ? 1 : 0);
}, 0);
break;
default:
return "BAD unknown status flag: " + status;
}
parts.push(line);
}
- return "* STATUS " + args[0] + " (" + parts.join(' ') +
+ return "* STATUS \"" + args[0] + "\" (" + parts.join(' ') +
")\0OK STATUS completed";
},
APPEND : function (args) {
var mailbox = this._daemon.getMailbox(args[0]);
if (!mailbox)
return "NO [TRYCREATE] no such mailbox";
if (args.length == 3) {
if (args[1] instanceof Date) {
--- a/mailnews/test/fakeserver/maild.js
+++ b/mailnews/test/fakeserver/maild.js
@@ -451,16 +451,17 @@ nsMailReader.prototype = {
}
},
closeSocket : function () {
this._signalStop = true;
},
_realCloseSocket : function () {
this._isRunning = false;
+ this._output.close();
this._transport.close(Cr.NS_OK);
this._server.stopTest();
},
setMultiline : function (multi) {
this._multiline = multi;
},
--- a/mailnews/test/resources/mailDirService.js
+++ b/mailnews/test/resources/mailDirService.js
@@ -12,16 +12,18 @@
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
// Declare these globally for unit tests and be done with it.
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var CC = Components.Constructor;
+var gProfileDirProvider = null;
+
// keep things out of global scope where possible.
function initializeDirServer() {
const NS_APP_USER_PROFILE_50_DIR = "ProfD";
// Various functions common to the tests.
const MailTestDirServer = {
/*
* makeDirectoryService
@@ -65,18 +67,18 @@ function initializeDirServer() {
dump("Directory request for: " + prop + " that we (mailDirService.js)" +
" are not handling, leaving it to another handler.\n");
throw Components.results.NS_ERROR_FAILURE;
},
QueryInterface:
XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider])
};
-
- dirSvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(provider);
+ gProfileDirProvider = provider;
+ dirSvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(gProfileDirProvider);
}
};
// If there's no location registered for the profile directory, register one
var dirSvc = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
var profileDir;
try {
new file mode 100644
--- /dev/null
+++ b/mailnews/test/resources/mailShutdown.js
@@ -0,0 +1,44 @@
+
+/* Provides methods to make sure our test shuts down mailnews properly. */
+
+// Notifies everyone that the we're shutting down. This is needed to make sure
+// that e.g. the account manager closes and cleans up correctly. It is semi-fake
+// because we don't actually do any work to make sure the profile goes away, but
+// it will mimic the behaviour in the app sufficiently.
+//
+// See also http://developer.mozilla.org/en/docs/Observer_Notifications
+function postShutdownNotifications()
+{
+ var observerService = Cc["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+
+ // first give everyone a heads up about us shutting down. if someone wants
+ // to cancel this, our test should fail.
+ var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Components.interfaces.nsISupportsPRBool);
+ observerService.notifyObservers(cancelQuit, "quit-application-requested", null);
+ if (cancelQuit.data) {
+ do_throw("Cannot shutdown: Someone cancelled the quit request!");
+ }
+
+ // post all notifications in the right order. none of these are cancellable
+ var notifications = ["quit-application", "profile-change-net-teardown", "profie-change-teardown", "profile-before-change"];
+ notifications.forEach(function(notification) {
+ observerService.notifyObservers(null, notification, null)
+ });
+
+ // finally, the xpcom-shutdown notification is handled by XPCOM itself.
+}
+
+// First do a gc to let anything not being referenced be cleaned up.
+gc();
+
+// Now shut everything down.
+postShutdownNotifications();
+
+gProfileDir = null;
+if (gProfileDirProvider) {
+ var dirSvc = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties);
+ dirSvc.unregisterProvider(gProfileDirProvider);
+ }
--- a/mailnews/test/resources/viewWrapperTestUtils.js
+++ b/mailnews/test/resources/viewWrapperTestUtils.js
@@ -1,14 +1,63 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
Components.utils.import("resource://app/modules/dbViewWrapper.js");
Components.utils.import("resource://app/modules/mailViewManager.js");
Components.utils.import("resource://app/modules/quickSearchManager.js");
Components.utils.import("resource://app/modules/virtualFolderWrapper.js");
+var gMessageGenerator, gMessageScenarioFactory;
+
+/**
+ * Do initialization for xpcshell-tests; not used by
+ * test-folder-display-helpers.js, our friendly mozmill test helper.
+ */
+function initViewWrapperTestUtils() {
+ gMessageGenerator = new MessageGenerator();
+ gMessageScenarioFactory = new MessageScenarioFactory(gMessageGenerator);
+
+ async_test_runner_register_helper(VWTU_testHelper);
+}
+
// something less sucky than do_check_true
function assert_true(aBeTrue, aWhy, aDumpView) {
if (!aBeTrue) {
if (aDumpView)
dump_view_state(VWTU_testHelper.active_view_wrappers[0]);
do_throw(aWhy);
}
}
@@ -24,27 +73,37 @@ function assert_false(aBeFalse, aWhy, aD
function assert_equals(aA, aB, aWhy, aDumpView) {
if (aA != aB) {
if (aDumpView)
dump_view_state(VWTU_testHelper.active_view_wrappers[0]);
do_throw(aWhy);
}
}
+function assert_bit_set(aWhat, aBit, aWhy) {
+ if (!(aWhat & aBit))
+ do_throw(aWhy);
+}
+
+function assert_bit_not_set(aWhat, aBit, aWhy) {
+ if (aWhat & aBit)
+ do_throw(aWhy);
+}
+
var gFakeCommandUpdater = {
- updateCommandStatus : function()
- {
+ updateCommandStatus : function() {
},
- displayMessageChanged : function(aFolder, aSubject, aKeywords)
- {
+ displayMessageChanged : function(aFolder, aSubject, aKeywords) {
},
- updateNextMessageAfterDelete : function()
- {
+ summarizeSelection: function () {
+ },
+
+ updateNextMessageAfterDelete : function() {
}
};
var gMockViewWrapperListener = {
__proto__: IDBViewWrapperListener.prototype,
shouldUseMailViews: true,
shouldDeferMessageDisplayUntilAfterServerConnect: false,
messenger: null,
@@ -83,18 +142,34 @@ var VWTU_testHelper = {
active_virtual_folders: [],
postTest: function () {
// close all the views we opened
this.active_view_wrappers.forEach(function (wrapper) {
wrapper.close();
});
// verify that the notification helper has no outstanding listeners.
- if (IDBViewWrapperListener.prototype._FNH.haveListeners())
- do_throw("FolderNotificationHelper has listeners, but should not.");
+ if (IDBViewWrapperListener.prototype._FNH.haveListeners()) {
+ let msg = "FolderNotificationHelper has listeners, but should not.";
+ dump("*** " + msg + "\n");
+ dump("Pending URIs:\n");
+ for each (let [folderURI, wrappers] in
+ Iterator(IDBViewWrapperListener.prototype._FNH
+ ._pendingFolderUriToViewWrapperLists)) {
+ dump(" " + folderURI + "\n");
+ }
+ dump("Interested wrappers:\n");
+ for each (let [folderURI, wrappers] in
+ Iterator(IDBViewWrapperListener.prototype._FNH
+ ._interestedWrappers)) {
+ dump(" " + folderURI + "\n");
+ }
+ dump("***\n");
+ do_throw(msg);
+ }
// force the folder to forget about the message database
this.active_virtual_folders.forEach(function (folder) {
folder.msgDatabase = null;
});
this.active_real_folders.forEach(function (folder) {
folder.msgDatabase = null;
});
@@ -115,17 +190,16 @@ var VWTU_testHelper = {
}
for each (let [i, viewWrapper] in Iterator(this.active_view_wrappers)) {
dump("-----------------------------------\n");
dump("Active view wrapper " + i + "\n");
dump_view_state(viewWrapper);
}
}
};
-async_test_runner_register_helper(VWTU_testHelper);
function make_view_wrapper() {
let wrapper = new DBViewWrapper(gMockViewWrapperListener);
VWTU_testHelper.active_view_wrappers.push(wrapper);
return wrapper;
}
/**
@@ -457,16 +531,34 @@ function verify_view_level_histogram(aEx
dump("Expected count for histogram level " + level + " was " + count +
" but got " + actualHisto[level] + "\n");
do_throw("View histogram does not match!");
}
}
}
/**
+ * Given a view wrapper and one or more view indices, verify that the row
+ * returns true for isContainer.
+ *
+ * @param aViewWrapper The view wrapper in question
+ * @param ... View indices to check.
+ */
+function verify_view_row_at_index_is_container(aViewWrapper) {
+ let treeView = aViewWrapper.dbView.QueryInterface(Ci.nsITreeView);
+ for (let iArg = 1; iArg < arguments.length; iArg++) {
+ let viewIndex = arguments[iArg];
+ if (!treeView.isContainer(viewIndex)) {
+ dump_view_state(aViewWrapper);
+ do_throw("Expected isContainer to be true at view index " + viewIndex);
+ }
+ }
+}
+
+/**
* Given a view wrapper and one or more view indices, verify that there is a
* dummy header at each provided index.
*
* @param aViewWrapper The view wrapper in question
* @param ... View indices to check.
*/
function verify_view_row_at_index_is_dummy(aViewWrapper) {
for (let iArg = 1; iArg < arguments.length; iArg++) {
@@ -525,18 +617,16 @@ function make_folders_with_sets(aFolderC
let msgFolders = [];
for (let i = 0; i < aFolderCount; i++)
msgFolders.push(make_empty_folder());
let results = make_new_sets_in_folders(msgFolders, aSynSetDefs);
results.unshift(msgFolders);
return results;
}
-var gMessageGenerator = new MessageGenerator();
-var gMessageScenarioFactory = new MessageScenarioFactory(gMessageGenerator);
/**
* Given one or more existing local folder, create new message sets and add them
* to the folders using
*
* @param aMsgFolders A single nsIMsgLocalMailFolder or a list of them. The
* synthetic messages will be added to the folder(s).
* @param aSynSetDefs Either an integer describing the number of sets of
* messages to create (using default parameters), or a list of set
@@ -625,16 +715,17 @@ function add_sets_to_folders(aMsgFolders
// loop, incrementing our subscript until all message sets are out of messages
let didSomething;
do {
didSomething = false;
// for each message set, if it is not out of messages, add the message
for each (let [, messageSet] in Iterator(aMessageSets)) {
if (iPerSet < messageSet.synMessages.length) {
messageSet.addMessageToFolderByIndex(folder, iPerSet);
+ folder.hasNewMessages = true;
didSomething = true;
}
}
iPerSet++;
folder = iterFolders.next();
} while (didSomething);
}
/** singular function name for understandability of single-folder users */
--- a/suite/browser/browser-prefs.js
+++ b/suite/browser/browser-prefs.js
@@ -431,17 +431,17 @@ pref("extensions.blocklist.detailsURL",
// Symmetric (can be overridden by individual extensions) update preferences.
// e.g.
// extensions.{GUID}.update.enabled
// extensions.{GUID}.update.url
// extensions.{GUID}.update.interval
// .. etc ..
//
pref("extensions.update.enabled", true);
-pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%");
+pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%¤tAppVersion=%CURRENT_APP_VERSION%");
pref("extensions.update.interval", 86400); // Check for updates to Extensions and
// Themes every day
// Preferences for the Get Add-ons pane
pref("extensions.getAddons.showPane", true);
pref("extensions.getAddons.browseAddons", "https://addons.mozilla.org/%LOCALE%/%APP%");
pref("extensions.getAddons.maxResults", 5);
pref("extensions.getAddons.recommended.browseURL", "https://addons.mozilla.org/%LOCALE%/%APP%/recommended");
--- a/suite/browser/navigator.js
+++ b/suite/browser/navigator.js
@@ -1687,23 +1687,43 @@ function BrowserViewSourceOfURL(url, cha
{
// try to open a view-source window while inheriting the charset (if any)
openDialog("chrome://navigator/content/viewSource.xul",
"_blank",
"all,dialog=no",
url, charset, pageCookie);
}
-// doc=null for regular page info, doc=owner document for frame info.
+// doc - document to use for source, or null for the current tab
+// initialTab - id of the initial tab to display, or null for the first tab
function BrowserPageInfo(doc, initialTab)
{
+ if (!doc)
+ doc = window.content.document;
+ var relatedUrl = doc.location.toString();
+ var args = {doc: doc, initialTab: initialTab};
+
+ var wm = Components.classes['@mozilla.org/appshell/window-mediator;1']
+ .getService(Components.interfaces.nsIWindowMediator);
+ var enumerator = wm.getEnumerator("Browser:page-info");
+ // Check for windows matching the url
+ while (enumerator.hasMoreElements()) {
+ let win = enumerator.getNext();
+ if (win.document.documentElement
+ .getAttribute("relatedUrl") == relatedUrl) {
+ win.focus();
+ win.resetPageInfo(args);
+ return win;
+ }
+ }
+ // We didn't find a matching window, so open a new one.
return window.openDialog("chrome://navigator/content/pageinfo/pageInfo.xul",
"_blank",
"chrome,dialog=no",
- {doc: doc, initialTab: initialTab});
+ args);
}
function hiddenWindowStartup()
{
// focus the hidden window
window.focus();
// Disable menus which are not appropriate
--- a/suite/browser/navigator.xul
+++ b/suite/browser/navigator.xul
@@ -279,16 +279,18 @@
event.stopPropagation();
HandleBookmarkIcon(this.src, true);"
onerror="gBrowser.addToMissedIconCache(this.src); HandleBookmarkIcon(this.src, false);"
tooltiptext="&proxyIcon.tooltip;"/>
</deck>
<hbox id="urlbar-icons"
class="urlbar-icons">
<image id="feedsButton" hidden="true" popup="feedsPopup"/>
+ <image id="ev-button" hidden="true"
+ onclick="if (event.button == 0) BrowserPageInfo(null, 'securityTab');"/>
</hbox>
<menupopup id="ubhist-popup" class="autocomplete-history-popup"
popupalign="topleft" popupanchor="bottomleft"
onpopupshowing="createUBHistoryMenu(event.target);"
oncommand="executeUrlBarHistoryCommand(event.target);"/>
</textbox>
<button id="go-button" class="button-toolbar chromeclass-location"
--- a/suite/browser/nsBrowserStatusHandler.js
+++ b/suite/browser/nsBrowserStatusHandler.js
@@ -69,16 +69,17 @@ nsBrowserStatusHandler.prototype =
this.statusMeter = document.getElementById("statusbar-icon");
this.statusPanel = document.getElementById("statusbar-progresspanel");
this.stopButton = document.getElementById("stop-button");
this.stopMenu = document.getElementById("menuitem-stop");
this.stopContext = document.getElementById("context-stop");
this.statusTextField = document.getElementById("statusbar-display");
this.isImage = document.getElementById("isImage");
this.securityButton = document.getElementById("security-button");
+ this.evButton = document.getElementById("ev-button");
this.feedsMenu = document.getElementById("feedsMenu");
this.feedsButton = document.getElementById("feedsButton");
// Initialize the security button's state and tooltip text
const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
this.onSecurityChange(null, null, nsIWebProgressListener.STATE_IS_INSECURE);
},
@@ -90,16 +91,17 @@ nsBrowserStatusHandler.prototype =
this.statusMeter = null;
this.statusPanel = null;
this.stopButton = null;
this.stopMenu = null;
this.stopContext = null;
this.statusTextField = null;
this.isImage = null;
this.securityButton = null;
+ this.evButton = null;
this.feedsButton = null;
this.feedsMenu = null;
},
setJSStatus : function(status)
{
this.jsStatus = status;
this.updateStatusField();
@@ -390,24 +392,29 @@ nsBrowserStatusHandler.prototype =
}
var securityUI = getBrowser().securityUI;
if (securityUI)
this.securityButton.setAttribute("tooltiptext", securityUI.tooltipText);
else
this.securityButton.removeAttribute("tooltiptext");
- if (aState & wpl.STATE_IDENTITY_EV_TOPLEVEL)
- this.securityButton.setAttribute("label",
+ if (aState & wpl.STATE_IDENTITY_EV_TOPLEVEL) {
+ var organization =
securityUI.QueryInterface(Components.interfaces.nsISSLStatusProvider)
.SSLStatus
.QueryInterface(Components.interfaces.nsISSLStatus)
- .serverCert.organization);
- else
+ .serverCert.organization;
+ this.securityButton.setAttribute("label", organization);
+ this.evButton.setAttribute("tooltiptext", organization);
+ this.evButton.hidden = false;
+ } else {
this.securityButton.removeAttribute("label");
+ this.evButton.hidden = true;
+ }
},
startDocumentLoad : function(aRequest)
{
var uri = aRequest.QueryInterface(Components.interfaces.nsIChannel).URI;
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
--- a/suite/browser/pageinfo/feeds.xml
+++ b/suite/browser/pageinfo/feeds.xml
@@ -50,17 +50,24 @@
<binding id="feed" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
<content>
<xul:vbox flex="1">
<xul:hbox flex="1">
<xul:textbox flex="1" readonly="true" xbl:inherits="value=name"
class="feedTitle"/>
<xul:label xbl:inherits="value=type"/>
</xul:hbox>
- <xul:textbox xbl:inherits="value=feedURL" readonly="true"/>
+ <xul:vbox align="start" flex="1">
+ <xul:hbox>
+ <xul:label xbl:inherits="value=feedURL,tooltiptext=feedURL"
+ class="text-link"
+ flex="1"
+ onclick="openUILink(this.value, event);" crop="end"/>
+ </xul:hbox>
+ </xul:vbox>
<xul:hbox flex="1" class="feed-subscribe">
<xul:spacer flex="1"/>
<xul:button label="&feedSubscribe;" accesskey="&feedSubscribe.accesskey;"
oncommand="onSubscribeFeed(event)"/>
</xul:hbox>
</xul:vbox>
</content>
</binding>
--- a/suite/browser/pageinfo/pageInfo.js
+++ b/suite/browser/pageinfo/pageInfo.js
@@ -1029,16 +1029,23 @@ function makePreview(row)
item instanceof HTMLEmbedElement ||
item instanceof HTMLLinkElement)
mimeType = item.type;
if (!mimeType && item instanceof nsIImageLoadingContent)
[mimeType, numFrames] = getContentTypeFromImgRequest(item);
if (!mimeType)
mimeType = getContentTypeFromHeaders(cacheEntryDescriptor);
+ // if we have a data url, get the MIME type from the url
+ if (!mimeType) {
+ var dataMimeType = /^data:(image\/.*?)[;,]/i.exec(url);
+ if (dataMimeType)
+ mimeType = dataMimeType[1].toLowerCase();
+ }
+
var imageType;
if (mimeType) {
// We found the type, try to display it nicely
let imageMimeType = /^image\/(.*)/.exec(mimeType);
if (imageMimeType) {
imageType = imageMimeType[1].toUpperCase();
if (numFrames > 1)
imageType = gBundle.getFormattedString("mediaAnimatedImageType",
@@ -1058,29 +1065,32 @@ function makePreview(row)
setItemValue("imagetypetext", imageType);
var imageContainer = document.getElementById("theimagecontainer");
var oldImage = document.getElementById("thepreviewimage");
const regex = /^(https?|ftp|file|gopher|about|chrome|resource):/;
var isProtocolAllowed = regex.test(url);
- if (/^data:/.test(url) && /^image\//.test(mimeType))
+ var isImageType = /^image\//.test(mimeType);
+ if (/^data:/.test(url) && isImageType)
isProtocolAllowed = true;
var newImage = new Image();
newImage.setAttribute("id", "thepreviewimage");
var physWidth = 0, physHeight = 0;
var width = 0, height = 0;
if ((item instanceof HTMLLinkElement ||
item instanceof HTMLInputElement ||
item instanceof HTMLImageElement ||
item instanceof SVGImageElement ||
- (item instanceof HTMLObjectElement && /^image\//.test(mimeType)) || isBG) && isProtocolAllowed) {
+ (item instanceof HTMLObjectElement && isImageType) ||
+ (item instanceof HTMLEmbedElement && isImageType) ||
+ isBG) && isProtocolAllowed) {
newImage.setAttribute("src", url);
physWidth = newImage.width || 0;
physHeight = newImage.height || 0;
if (item instanceof SVGImageElement) {
newImage.width = item.width.baseVal.value;
newImage.height = item.height.baseVal.value;
}
--- a/suite/browser/pageinfo/pageInfo.xul
+++ b/suite/browser/pageinfo/pageInfo.xul
@@ -96,18 +96,18 @@
<key key="." modifiers="meta" command="cmd_close"/>
<key keycode="VK_F1" command="cmd_help"/>
<key key="©.key;" modifiers="accel" command="cmd_copy"/>
<key key="&selectall.key;" modifiers="accel" command="cmd_selectall"/>
<key key="&selectall.key;" modifiers="alt" command="cmd_selectall"/>
</keyset>
<menupopup id="picontext">
- <menuitem label="&selectall.label;" command="cmd_selectall" accesskey="&selectall.accesskey;"/>
- <menuitem label="©.label;" command="cmd_copy" accesskey="©.accesskey;"/>
+ <menuitem id="menu_selectall" label="&selectall.label;" command="cmd_selectall" accesskey="&selectall.accesskey;"/>
+ <menuitem id="menu_copy" label="©.label;" command="cmd_copy" accesskey="©.accesskey;"/>
</menupopup>
<tabbox id="tabbox" flex="1">
<tabs id="tabs" onselect="[gImageView, gFormView, gLinkView].forEach(ensureSelection);">
<tab id="generalTab" label="&generalTab;" accesskey="&generalTab.accesskey;"/>
<tab id="mediaTab" label="&mediaTab;" accesskey="&mediaTab.accesskey;"/>
<tab id="feedTab" label="&feedTab;" accesskey="&feedTab.accesskey;"/>
<tab id="permTab" label="&permTab;" accesskey="&permTab.accesskey;"/>
@@ -185,18 +185,18 @@
<treechildren flex="1"/>
</tree>
</groupbox>
<groupbox id="securityBox">
<caption id="securityBoxCaption" label="&securityHeader;"/>
<description id="general-security-identity" class="indent header"/>
<description id="general-security-privacy" class="indent header"/>
<hbox align="right">
- <button id="security-view-more" label="&generalSecurityMore;"
- accesskey="&generalSecurityMore.accesskey;"
+ <button id="security-view-details" label="&generalSecurityDetails;"
+ accesskey="&generalSecurityDetails.accesskey;"
oncommand="onClickMore();"/>
</hbox>
</groupbox>
</vbox>
<!-- Media information -->
<vbox id="mediaPanel">
<tree id="imagetree" onselect="onImageSelect();" contextmenu="picontext"
--- a/suite/common/nsContextMenu.js
+++ b/suite/common/nsContextMenu.js
@@ -1157,24 +1157,28 @@ nsContextMenu.prototype = {
case "hidecontrols":
media.removeAttribute("controls");
break;
case "showcontrols":
media.setAttribute("controls", "true");
break;
}
},
-
copyMediaLocation : function () {
var clipboard = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
.getService(Components.interfaces.nsIClipboardHelper);
clipboard.copyString(this.mediaURL);
+ },
+
+ get imageURL() {
+ if (this.onImage)
+ return this.mediaURL;
+ return "";
}
};
-
/*************************************************************************
*
* nsDefaultEngine : nsIObserver
*
*************************************************************************/
function nsDefaultEngine()
{
try
--- a/suite/debugQA/content/debugQAMenuOverlay.xul
+++ b/suite/debugQA/content/debugQAMenuOverlay.xul
@@ -211,19 +211,19 @@
<menuitem label="File a Bug"
oncommand="openTopWin('https://bugzilla.mozilla.org/enter_bug.cgi?format=guided');"/>
<menuseparator/>
<menuitem label="Bugs Filed Today"
oncommand="openTopWin('https://bugzilla.mozilla.org/buglist.cgi?product=Core&product=MailNews+Core&product=SeaMonkey&chfieldfrom=0d&chfieldto=Now&chfield=%5BBug+creation%5D');"/>
<menuitem label="Recent comm-central Checkins"
- oncommand="openTopWin('http://hg.mozilla.org/comm-central/index.cgi/pushloghtml?startdate=24+hours+ago&enddate=now');"/>
+ oncommand="openTopWin('http://hg.mozilla.org/comm-central/pushloghtml?startdate=24+hours+ago&enddate=now');"/>
<menuitem label="Recent mozilla-central Checkins"
- oncommand="openTopWin('http://hg.mozilla.org/mozilla-central/index.cgi/pushloghtml?startdate=24+hours+ago&enddate=now');"/>
+ oncommand="openTopWin('http://hg.mozilla.org/mozilla-central/pushloghtml?startdate=24+hours+ago&enddate=now');"/>
<menuitem label="Tree Status"
oncommand="openTopWin('http://tinderbox.mozilla.org/showbuilds.cgi?tree=SeaMonkey');"/>
<menuseparator/>
<menuitem label="Smoke Tests"
oncommand="openTopWin('http://www.mozilla.org/quality/smoketests/');"/>
<menuitem label="Pre-Checkin Tests"
--- a/suite/installer/removed-files.in
+++ b/suite/installer/removed-files.in
@@ -91,26 +91,28 @@ defaults/wallet/VcardSchema.tbl
defaults/wallet/FieldSchema.tbl
defaults/wallet/SchemaConcat.tbl
defaults/wallet/DistinguishedSchema.tbl
defaults/wallet/SchemaStrings.tbl
defaults/wallet/PositionalSchema.tbl
defaults/wallet/StateSchema.tbl
modules/JSON.jsm
@DLL_PREFIX@xpistub@DLL_SUFFIX@
+@DLL_PREFIX@mozlcms@DLL_SUFFIX@
res/cmessage.txt
LICENSE
#ifdef XP_WIN
#ifdef MOZ_MEMORY
Microsoft.VC80.CRT.manifest
msvcm80.dll
msvcp80.dll
msvcr80.dll
#endif
xpicleanup.exe
+regxpcom.exe
#ifndef MOZILLA_1_9_1_BRANCH
components/nsPostUpdateWin.js
#endif
#else
xpicleanup
#endif
#ifndef MOZILLA_1_9_1_BRANCH
res/broken-image.gif
@@ -182,16 +184,23 @@ components/@DLL_PREFIX@xpconnect@DLL_SUF
components/@DLL_PREFIX@zipwriter@DLL_SUFFIX@
@DLL_PREFIX@gfxpsshar@DLL_SUFFIX@
@DLL_PREFIX@gkgfx@DLL_SUFFIX@
@DLL_PREFIX@gtkxtbin@DLL_SUFFIX@
@DLL_PREFIX@jsj@DLL_SUFFIX@
@DLL_PREFIX@mozz@DLL_SUFFIX@
@DLL_PREFIX@thebes@DLL_SUFFIX@
@DLL_PREFIX@xul@DLL_SUFFIX@
+#ifdef XP_MACOSX
+XUL
+components/libimgicon.dylib
+components/libjar50.dylib
+components/libosxproxy.dylib
+components/libwidget_mac.dylib
+#endif
#ifdef XP_WIN
gksvggdiplus.dll
jsj3250.dll
components/appshell.dll
components/cmdlines.dll
components/gkparser.dll
components/gkwidget.dll
components/imgicon.dll
--- a/suite/locales/en-US/chrome/browser/pageInfo.dtd
+++ b/suite/locales/en-US/chrome/browser/pageInfo.dtd
@@ -55,18 +55,18 @@
<!ENTITY generalMode "Render Mode:">
<!ENTITY generalSize "Size:">
<!ENTITY generalReferrer "Referring URL:">
<!ENTITY generalSource "Cache Source:">
<!ENTITY generalModified "Modified:">
<!ENTITY generalEncoding "Encoding:">
<!ENTITY generalMetaName "Name">
<!ENTITY generalMetaContent "Content">
-<!ENTITY generalSecurityMore "More">
-<!ENTITY generalSecurityMore.accesskey "o">
+<!ENTITY generalSecurityDetails "Details">
+<!ENTITY generalSecurityDetails.accesskey "D">
<!ENTITY formsTab "Forms">
<!ENTITY formsTab.accesskey "F">
<!ENTITY formAction "Form Action">
<!ENTITY formMethod "Method">
<!ENTITY formName "Name">
<!ENTITY formEncoding "Encoding:">
<!ENTITY formTarget "Target:">
--- a/suite/locales/en-US/chrome/common/help/cs_nav_prefs_navigator.xhtml
+++ b/suite/locales/en-US/chrome/common/help/cs_nav_prefs_navigator.xhtml
@@ -131,31 +131,45 @@
<ol>
<li>Open the <span class="mac">&brandShortName;</span>
<span class="noMac">Edit</span>menu and choose Preferences.</li>
<li>Under the Browser category, click History. (If no subcategories are
visible, double-click Browser to expand the list.)</li>
</ol>
-<p>The History preferences panel allows you to configure three history settings
+<p>The History preferences panel allows you to configure the history settings
for the browser.</p>
<ul>
<li><strong>Browsing History</strong>:
<ul>
- <li><strong>Remember visited pages for the last [__] days</strong>: Type
- the number of days you want &brandShortName; to keep track of the web
- pages you have previously visited. For example, if you set this number
- to 10 days, pages 10 days old or less will be kept in the history
- list.</li>
+ <li><strong>Clear History</strong>: Click this to delete the list of
+ sites visited.</li>
+ <li><strong>Always remember visited pages for at least [__]
+ days</strong>: Type the minimum number of days for which
+ &brandShortName; should remember pages you have previously visited.
+ Unless you manually delete entries from the history, &brandShortName;
+ will always remember visited pages for at least as many days as you
+ specify here, no matter what the other Browsing History preferences
+ say. After that time, &brandShortName; may delete entries from the
+ history, depending on the next two preferences.</li>
+ <li><strong>Remember visited pages for up to [__] days</strong>: Type the
+ maximum number of days you want &brandShortName; to keep track of the
+ web pages you have previously visited. For example, if you set this
+ number to 180 days, pages 180 days old or less will be kept in the
+ history list unless you manually delete them from the history or the
+ maximum number of visited pages (see next point) has been reached.</li>
+ <li><strong>Remember up to [__] visited pages</strong>: Type the maximum
+ number of visited pages &brandShortName; should remember. When the
+ actual number of visited pages exceeds the number specified here,
+ &brandShortName; will delete as many of the oldest history entries as
+ needed to get down to that number again.</li>
</ul>
</li>
- <li><strong>Clear History</strong>: Click this to delete the list of sites
- visited.</li>
<li><strong>Location Bar History</strong>:
<ul>
<li><strong>Clear Location Bar</strong>: Click this to clear the list of
sites in the Location bar menu.</li>
</ul>
</li>
</ul>
@@ -288,20 +302,47 @@
<ul>
<li id="location_bar_autocomplete"><strong>Autocomplete</strong>:
<ul>
<li><strong>Autocomplete from your browsing history as you type</strong>:
Select this to let &brandShortName; automatically show suggestions from
your browsing history when you type in the Location Bar.
<ul>
- <li><strong>Match only website's you've typed
- previously</strong>: Shows only websites that you've typed in
- the Location Bar and not sites that were opened in other ways, such
- as clicking a link on a web page.</li>
+ <li><strong>Match only websites you've typed previously</strong>:
+ Shows only websites that you've typed in the Location Bar and
+ not sites that were opened in other ways, such as clicking a link on
+ a web page.</li>
+ <li><strong>Only match locations, not website titles</strong>: Shows
+ only websites where the location matches what you typed. Websites
+ where the title matches what you typed will not show up as
+ autocomplete suggestions unless their location matches, too.</li>
+ <li><strong>Match</strong>:
+ <ul>
+ <li><strong>Anywhere in the location or title</strong>: The
+ autocomplete suggestions will include all websites where what
+ you typed matches any part of the website's location or
+ title.</li>
+ <li><strong>Anywhere but preferring word boundaries</strong>: The
+ autocomplete suggestions will include all websites where what
+ you typed matches any part of the website's location or
+ title but matches at word boundaries (see next point) are
+ preferred. This is the default setting.</li>
+ <li><strong>Only on word boundaries</strong>: The autocomplete
+ suggestions will include all websites where what you typed
+ matches the beginning of any word contained in the
+ website's location or title. Matches may also be found
+ inside a word if it contains medial capital letters (as in
+ CamelCase) since all non-lowercase characters are treated as
+ word boundaries.</li>
+ <li><strong>Only at the beginning of the location or
+ title</strong>: The autocomplete suggestions will include all
+ websites where what you typed matches the beginning of the
+ website's location or title.</li>
+ </ul></li>
<li><strong>Automatically prefill the best match</strong>: As you
type in the Location Bar, &brandShortName; will automatically
complete your web address using the visited website it most closely
matches. <span class="unix"><strong>Note</strong>: Having this
option on will prefill local addresses (like paths to files on your
hard drive) even if you have turned off <q>Autocomplete from your
browsing history as you type</q>.</span></li>
<li><strong>Show list of matching results</strong>: As you type in
--- a/suite/locales/en-US/chrome/common/help/mail_help.xhtml
+++ b/suite/locales/en-US/chrome/common/help/mail_help.xhtml
@@ -4103,36 +4103,35 @@ to filter unwanted mail, and how phishin
<p>&brandShortName; can automatically delete old messages for you. You
can configure this process with the options listed below
<strong>To recover disk space, old messages can be permanently
deleted</strong>:</p>
<ul>
<li><strong>Don't delete any messages</strong>: Keep all messages. Never
- delete messages automatically.</li>
- <li><strong>Delete all but the last [__] messages</strong>: Enter the number
- of messages to keep. With this setting only messages older than these
+ delete messages automatically based on their age.</li>
+ <li><strong>Delete all but the most recent [__] messages</strong>: Enter the
+ number of messages to keep. With this setting only messages older than these
messages are deleted.</li>
<li><strong>Delete messages more than [__] days old </strong>:
Keep all messages that arrived within the given number of days.</li>
</ul>
<p>With the following settings you can further constrain the three options to
delete messages automatically. This is especially useful in combination with
the option to keep all messages.</p>
<ul>
- <li><strong>Always delete read messages</strong>: Select this option to
- remove read messages.</li>
<li><strong>Always keep flagged messages</strong>: Use this option to deny
&brandShortName; to delete any messages you have flagged.</li>
- <li><strong>Only message bodies less than [__] days old</strong>: Select this
- option to deny &brandShortName; the deletion of messages that are newer
- than the number of days you specify here (news accounts only).</li>
+ <li><strong>Remove bodies from message more than [__] days old</strong>:
+ Select this option to retain all headers but to delete message bodies that
+ are older than the number of days you specify here (news accounts only).
+ Any option to delete the entire message based on age still applies.</li>
</ul>
<p>This policy can be overridden for an individual folder in the Folder
Properties, Retention Policy tab.</p>
<p><strong>Note:</strong> If message synchronization is enabled (for IMAP), or
messages are left on the server for POP accounts), the settings apply to
<em>both</em> local copies and their originals on the server.</p>
--- a/suite/locales/en-US/chrome/common/help/nav_help.xhtml
+++ b/suite/locales/en-US/chrome/common/help/nav_help.xhtml
@@ -218,19 +218,19 @@
<span class="mac">&brandShortName;</span><span class="noMac">Edit</span>
menu and choose Preferences.</li>
<li>Under the Browser category, click History. (If no subcategories are
visible, double-click Browser to expand the list.)</li>
<li>Click Clear History and Clear Location Bar to remove all previously
visited web pages from the lists.</li>
</ol>
-<p><strong>Tip</strong>: In <q>Remember visited pages for the last [__]
- days</q>, you can set the number of days pages will remain in the history
- list.</p>
+<p><strong>Tip</strong>: Use the preferences under <a
+ href="cs_nav_prefs_navigator.xhtml#history">Browsing History</a> to set
+ how long and how many pages will remain in the history list.</p>
<p>To selectively delete pages from the history list, do any of the
following:</p>
<ul>
<li>To delete all pages from a domain, select a page within that domain
(folder) in the History list, open the Edit menu, and select <q>Delete
entire domain <em>*.[domain name]</em></q>. For example, use this command
deleted file mode 100644
--- a/suite/locales/en-US/chrome/common/pref/pref-applications-edit.dtd
+++ /dev/null
@@ -1,24 +0,0 @@
-
-<!ENTITY editType.label "Edit Type">
-<!ENTITY newType.label "New Type">
-<!ENTITY extension.label "Extension:">
-<!ENTITY extension.accesskey "E">
-<!--LOCALIZATION NOTE (mimeType): 'MIME' should not be translated -->
-<!ENTITY mimetype.label "MIME Type:">
-<!ENTITY mimetype.accesskey "m">
-<!ENTITY description.label "Description:">
-<!ENTITY description.accesskey "d">
-
-<!ENTITY handling.label "When a file of this type is encountered">
-<!ENTITY useDefault.label "Open it using the default application">
-<!ENTITY useDefault.accesskey "o">
-<!ENTITY saveToDisk.label "Save it to Disk">
-<!ENTITY saveToDisk.accesskey "s">
-<!ENTITY application.label "Open it with:">
-<!ENTITY application.accesskey "w">
-
-<!ENTITY browse.label "Choose...">
-<!ENTITY browse.accesskey "c">
-
-<!ENTITY askBeforeOpen.label "Always ask me before handling files of this type">
-<!ENTITY askBeforeOpen.accesskey "k">
--- a/suite/locales/en-US/chrome/mailnews/folderProps.dtd
+++ b/suite/locales/en-US/chrome/mailnews/folderProps.dtd
@@ -56,20 +56,20 @@
<!ENTITY message.label "messages">
<!ENTITY retentionCleanup.label "Keep messages:">
<!ENTITY retentionCleanupImap.label "Keep messages, both the local copies and their originals on the server:">
<!ENTITY retentionCleanupPop.label "Keep messages, including their originals on the server:">
<!ENTITY retentionDeleteMsg.label "Delete messages more than">
<!ENTITY retentionDeleteMsg.accesskey "m">
<!ENTITY retentionKeepAll.label "All messages">
<!ENTITY retentionKeepAll.accesskey "A">
-<!ENTITY retentionKeepNew.label "The newest">
-<!ENTITY retentionKeepNew.accesskey "n">
-<!ENTITY retentionKeepUnread.label "Only unread messages">
-<!ENTITY retentionKeepUnread.accesskey "u">
+<!ENTITY retentionKeepRecent.label "The newest">
+<!ENTITY retentionKeepRecent.accesskey "n">
+<!-- LOCALIZATION NOTE: Unhide with .keepUnreadOnly { display: -moz-box; } -->
+<!ENTITY retentionKeepUnreadHidden.label "Always delete read messages (overrides age settings)">
<!ENTITY retentionApplyToFlagged.label "Always keep flagged messages">
<!ENTITY retentionApplyToFlagged.accesskey "e">
<!ENTITY folderSynchronizationTab.label "Synchronization">
<!ENTITY folderCheckForNewMessages.label "Check this folder for new messages">
<!ENTITY folderCheckForNewMessages.accesskey "C">
<!ENTITY offlineFolder.check.label "Select this folder for offline use">
--- a/suite/locales/en-US/chrome/mailnews/pref/am-offline.dtd
+++ b/suite/locales/en-US/chrome/mailnews/pref/am-offline.dtd
@@ -13,22 +13,22 @@
<!ENTITY nntpDownloadMsg.accesskey "e">
<!ENTITY retentionCleanup.label "To recover disk space, old messages can be permanently deleted.">
<!ENTITY retentionCleanupImap.label "To recover disk space, old messages can be permanently deleted, both the local copies and their originals on the server.">
<!ENTITY retentionCleanupPop.label "To recover disk space, old messages can be permanently deleted, including their originals on the server.">
<!ENTITY retentionKeepMsg.label "Delete messages more than">
<!ENTITY retentionKeepMsg.accesskey "t">
<!ENTITY retentionKeepAll.label "Don't delete any messages">
<!ENTITY retentionKeepAll.accesskey "n">
-<!ENTITY retentionKeepNew.label "Delete all but the last">
-<!ENTITY retentionKeepNew.accesskey "b">
-<!ENTITY retentionKeepUnread.label "Always delete read messages">
-<!ENTITY retentionKeepUnread.accesskey "w">
+<!ENTITY retentionKeepRecent.label "Delete all but the most recent">
+<!ENTITY retentionKeepRecent.accesskey "b">
+<!-- LOCALIZATION NOTE: Unhide with .keepUnreadOnly { display: -moz-box; } -->
+<!ENTITY retentionKeepUnreadHidden.label "Always delete read messages (overrides age settings)">
<!ENTITY retentionApplyToFlagged.label "Always keep flagged messages">
<!ENTITY retentionApplyToFlagged.accesskey "k">
-<!ENTITY nntpRemoveBody.label "Only message bodies less than">
-<!ENTITY nntpRemoveBody.accesskey "O">
+<!ENTITY nntpRemoveMsgBody.label "Remove bodies from messages more than">
+<!ENTITY nntpRemoveMsgBody.accesskey "o">
<!ENTITY offlineSelectNntp.label "Select newsgroups for offline use…">
<!ENTITY offlineSelectNntp.accesskey "S">
<!ENTITY offlineImapAdvancedOffline.label "Advanced…">
<!ENTITY offlineImapAdvancedOffline.accesskey "v">
<!ENTITY syncGroupTitle.label "Message Synchronizing">
<!ENTITY diskspaceGroupTitle.label "Disk Space">
--- a/suite/locales/en-US/installer/unix/README
+++ b/suite/locales/en-US/installer/unix/README
@@ -125,13 +125,13 @@ Installation Instructions
where directory_name is the name of the directory you downloaded
SeaMonkey to. For example, the default directory that SeaMonkey
suggests is /usr/local/seamonkey.
3. Type in a name for the icon, and type in a comment if you wish.
4. Click the icon button and type in the following as the icon's location:
- directory_name/chrome/icons/default/default.xpm
+ directory_name/chrome/icons/default/default.png
where directory_name is the directory where you installed SeaMonkey.
For example, the default directory is
- /usr/local/seamonkey/chrome/icons/default/default.xpm.
+ /usr/local/seamonkey/chrome/icons/default/default.png.
--- a/suite/locales/jar.mn
+++ b/suite/locales/jar.mn
@@ -172,16 +172,17 @@
locale/@AB_CD@/communicator/profile/profileSelection.dtd (%chrome/common/profile/profileSelection.dtd)
locale/@AB_CD@/communicator/profile/profileSelection.properties (%chrome/common/profile/profileSelection.properties)
locale/@AB_CD@/communicator/search/default.htm (%chrome/common/search/default.htm)
locale/@AB_CD@/communicator/search/internetresults.dtd (%chrome/common/search/internetresults.dtd)
locale/@AB_CD@/communicator/search/search-editor.dtd (%chrome/common/search/search-editor.dtd)
locale/@AB_CD@/communicator/search/search-editor.properties (%chrome/common/search/search-editor.properties)
locale/@AB_CD@/communicator/search/search-panel.dtd (%chrome/common/search/search-panel.dtd)
locale/@AB_CD@/communicator/search/search-panel.properties (%chrome/common/search/search-panel.properties)
+ locale/@AB_CD@/communicator/search/search-rdf.dtd (%chrome/common/search/search-rdf.dtd)
locale/@AB_CD@/communicator/sidebar/customize.dtd (%chrome/common/sidebar/customize.dtd)
locale/@AB_CD@/communicator/sidebar/local-panels.dtd (%chrome/common/sidebar/local-panels.dtd)
locale/@AB_CD@/communicator/sidebar/preview.dtd (%chrome/common/sidebar/preview.dtd)
locale/@AB_CD@/communicator/sidebar/sidebar.properties (%chrome/common/sidebar/sidebar.properties)
locale/@AB_CD@/communicator/sidebar/sidebarOverlay.dtd (%chrome/common/sidebar/sidebarOverlay.dtd)
locale/@AB_CD@/communicator-platform/win/platformCommunicatorOverlay.dtd (%chrome/common/win/platformCommunicatorOverlay.dtd)
locale/@AB_CD@/communicator-platform/unix/platformCommunicatorOverlay.dtd (%chrome/common/unix/platformCommunicatorOverlay.dtd)
locale/@AB_CD@/communicator-platform/mac/platformCommunicatorOverlay.dtd (%chrome/common/mac/platformCommunicatorOverlay.dtd)
new file mode 100644
--- /dev/null
+++ b/suite/locales/shipped-locales
@@ -0,0 +1,1 @@
+en-US
--- a/suite/mailnews/jar.mn
+++ b/suite/mailnews/jar.mn
@@ -32,16 +32,17 @@ messenger.jar:
content/messenger/mailWindow.js
content/messenger/messageWindow.xul
content/messenger/messageWindow.js
content/messenger/folderPane.xul
content/messenger/threadPane.xul
content/messenger/threadPane.js
content/messenger/msgHdrViewOverlay.xul
content/messenger/msgHdrViewOverlay.js
+ content/messenger/msgViewNavigation.js
content/messenger/widgetglue.js
content/messenger/commandglue.js
content/messenger/mailCommands.js
content/messenger/subscribe.xul
content/messenger/subscribe.js
content/messenger/msgMail3PaneWindow.js
content/messenger/searchBar.js
content/messenger/mail3PaneWindowCommands.js
--- a/suite/mailnews/mail3PaneWindowCommands.js
+++ b/suite/mailnews/mail3PaneWindowCommands.js
@@ -453,17 +453,17 @@ var DefaultController =
return IsFolderCharsetEnabled();
case "cmd_close":
return true;
case "cmd_downloadFlagged":
return(CheckOnline());
case "cmd_downloadSelected":
return (IsFolderSelected() && CheckOnline() && GetNumSelectedMessages() > 0);
case "cmd_synchronizeOffline":
- return CheckOnline() && IsAccountOfflineEnabled();
+ return CheckOnline();
case "cmd_settingsOffline":
return IsAccountOfflineEnabled();
default:
return false;
}
return false;
},
--- a/suite/mailnews/mailWindowOverlay.xul
+++ b/suite/mailnews/mailWindowOverlay.xul
@@ -2116,35 +2116,36 @@
command="cmd_nextFlaggedMsg"/>
<menuseparator/>
<menuitem label="&nextUnreadThread.label;"
accesskey="&nextUnreadThread.accesskey;"
command="cmd_nextUnreadThread"/>
</menupopup>
</toolbarbutton>
- <toolbaritem id="button-junk">
+ <toolbaritem id="button-junk"
+ observes="button_junk">
<deck id="junk-deck"
- observes="button_junk"
oncommand="goDoCommand('button_junk')">
<toolbarbutton id="button-isJunk"
class="toolbarbutton-1"
label="&junkButton.label;"
tooltiptext="&junkButton.tooltip;"
observes="button_junk"/>
<toolbarbutton id="button-notJunk"
class="toolbarbutton-1"
label="¬JunkButton.label;"
tooltiptext="¬JunkButton.tooltip;"
observes="button_junk"/>
</deck>
</toolbaritem>
- <toolbaritem id="button-delete">
- <deck id="delete-deck" observes="button_delete">
+ <toolbaritem id="button-delete"
+ observes="button_delete">
+ <deck id="delete-deck">
<toolbarbutton id="button-mark-deleted"
class="toolbarbutton-1"
label="&deleteButton.label;"
tooltiptext="&deleteButton.tooltip;"
observes="button_delete"
oncommand="goDoCommand(event.shiftKey ? 'button_shiftDelete' : 'button_delete')"/>
<toolbarbutton id="button-mark-undelete"
class="toolbarbutton-1"
copy from mailnews/base/resources/content/msgViewNavigation.js
copy to suite/mailnews/msgViewNavigation.js
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..dc35c2c6fe1c2de14cdfcfe13196716af1d12dcd
GIT binary patch
literal 616
zc$@)f0+;=XP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0006nNkl<ZILoDx
zZA(*O7{~ntz3^-F2?`8GAq5S~Rm{SuPY@_YQnqOgG8!tbGF0Xvnv4b8GR=l(x%IG|
z|Fw?p!NE3M?Chboxu>(W=(Ns#S7&0r2z>Fox$ggUUB4Ii-PUY2+x7!JzKPWX)-TsA
zv-}0@#3(VH=*865Maqm+lbp=f#ZtR0c$Vt7@bwKuDo!k9Z(}igM_N>4D$#?r^ee=3
zF$&Tw%}CG8U$M!8$=F%&wIHM#?{f_7ycQ&T6g>xP!~l->QCtZ-CZ&w@2(rMi*^Q(S
zpn}z}W>`Pp0?7=UDtIzxh@wc@Dh6N(tN!%5TY~XG7u;zh#GgM9EU<VS?ghz4xziZ)
ztHEVnBUOrHKCb_rt)HTOS)lhTao?xJ9H++&*9ZMaCGMdM&jSpC$si>p!T7asMqYsV
zq@gpiV8nX@cR!p&v6=^$07hJwG3T2=qtTGrNWX!7ouRcWZU)(r1-f^gAeaI-P^e~+
z$!8%50#*|ax{%zCV%jqdp(fC~Gis*uvS7${41XE*oJS<<MJVHeZsiQND>*!TdmX7f
zk9xgMS&o?KtSlI071)V}J&GY`C$#KwFut4c##T_O7Adt_4UX_r3@$5Lf`O$D?8$-x
z4_GZ`-QyIaYXap;8B4(@7<k#y5?q}>j6Ja_=v(Y&QF=_$Z~X$>W9hRU!fyH{Ol!mc
zpl2^Z=TO;YLG^o~eax%2YurchKakmEIdo7K?1x`Jd)k)upZ-$-0000<MNUMnLSTY(
CuOCwY
--- a/suite/themes/classic/jar.mn
+++ b/suite/themes/classic/jar.mn
@@ -73,16 +73,17 @@ classic.jar:
skin/classic/communicator/sidebar/sidebar.css (communicator/sidebar/sidebar.css)
skin/classic/communicator/sidebar/sidebarListView.css (communicator/sidebar/sidebarListView.css)
skin/classic/communicator/xpinstall/xpinstall.css (communicator/xpinstall/xpinstall.css)
skin/classic/communicator/icons/audioFeedIcon.png (communicator/icons/feedIcon.png)
skin/classic/communicator/icons/close-button.gif (communicator/icons/close-button.gif)
skin/classic/communicator/icons/offline.png (communicator/icons/offline.png)
skin/classic/communicator/icons/online.png (communicator/icons/online.png)
skin/classic/communicator/icons/search.png (communicator/icons/search.png)
+ skin/classic/communicator/icons/identity.png (communicator/icons/identity.png)
skin/classic/communicator/icons/lock-secure.png (communicator/icons/lock-secure.png)
skin/classic/communicator/icons/lock-broken.png (communicator/icons/lock-broken.png)
skin/classic/communicator/icons/lock-insecure.png (communicator/icons/lock-insecure.png)
skin/classic/communicator/icons/loading.gif (communicator/icons/loading.gif)
skin/classic/communicator/icons/communicatoricons.png (communicator/icons/communicatoricons.png)
skin/classic/communicator/icons/communicatoricons-small.png (communicator/icons/communicatoricons-small.png)
skin/classic/communicator/icons/alwaysAsk.png (communicator/icons/alwaysAsk.png)
skin/classic/communicator/icons/application.png (communicator/icons/application.png)
--- a/suite/themes/classic/messenger/messageBody.css
+++ b/suite/themes/classic/messenger/messageBody.css
@@ -160,22 +160,22 @@ span.moz-txt-formfeed {
cursor: -moz-zoom-out;
}
.moz-attached-image[isshrunk="true"] {
cursor: -moz-zoom-in;
max-width: 100%;
}
-/* Style new format rss summary vs web page */
-body[selected="false"],
-iframe[selected="false"] {
+/* New style feed summary body. */
+body[selected="false"] {
display: none;
}
-
+
+/* Old style feeds. */
#_mailrssiframe {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
--- a/suite/themes/classic/messenger/primaryToolbar.css
+++ b/suite/themes/classic/messenger/primaryToolbar.css
@@ -39,20 +39,16 @@
/* ===== primaryToolbar.css =============================================
== Images for the Mail primary toolbar.
======================================================================= */
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
/* ::::: primary toolbar buttons ::::: */
-.toolbarbutton-1 {
- list-style-image: url("chrome://messenger/skin/icons/messengericons.png");
-}
-
#button-getmsg {
list-style-image: url("chrome://messenger/skin/icons/messengericons.png");
-moz-image-region: rect(90px 29px 119px 0);
}
#button-getmsg:hover {
-moz-image-region: rect(90px 59px 119px 30px);
}
@@ -199,21 +195,21 @@
}
#button-next[disabled] {
-moz-image-region: rect(180px 119px 209px 90px) !important;
}
#button-delete {
list-style-image: url("chrome://messenger/skin/icons/messengericons.png");
- -moz-image-region: rect(0, 29px 29px 0);
+ -moz-image-region: rect(0 29px 29px 0);
}
#button-delete:hover {
- -moz-image-region: rect(0, 59px 29px 30px);
+ -moz-image-region: rect(0 59px 29px 30px);
}
#button-delete:hover:active {
-moz-image-region: rect(0 89px 29px 60px);
}
#button-delete[disabled] {
-moz-image-region: rect(0 119px 29px 90px) !important;
@@ -444,21 +440,21 @@ toolbar[iconsize="small"] > #button-next
}
toolbar[iconsize="small"] > #button-next[disabled] {
-moz-image-region: rect(120px 79px 139px 60px) !important;
}
toolbar[iconsize="small"] > #button-delete {
list-style-image: url("chrome://messenger/skin/icons/messengericons-small.png");
- -moz-image-region: rect(0, 19px 19px 0);
+ -moz-image-region: rect(0 19px 19px 0);
}
toolbar[iconsize="small"] > #button-delete:hover {
- -moz-image-region: rect(0, 39px 19px 20px);
+ -moz-image-region: rect(0 39px 19px 20px);
}
toolbar[iconsize="small"] > #button-delete:hover:active {
-moz-image-region: rect(0 59px 19px 40px);
}
toolbar[iconsize="small"] > #button-delete[disabled] {
-moz-image-region: rect(0 79px 19px 60px) !important;
@@ -476,30 +472,30 @@ toolbar[iconsize="small"] > #button-mark
toolbar[iconsize="small"] > #button-mark:hover:active {
-moz-image-region: rect(80px 59px 99px 40px);
}
toolbar[iconsize="small"] > #button-mark[disabled] {
-moz-image-region: rect(80px 79px 99px 60px) !important;
}
-toolbar[iconsize="small"] > #button-junk > #junk-deck > .toolbarbutton-1 {
+toolbar[iconsize="small"] > #button-junk {
list-style-image: url("chrome://messenger/skin/icons/messengericons-small.png");
-moz-image-region: rect(240px 19px 259px 0);
}
-toolbar[iconsize="small"] > #button-junk > #junk-deck:hover > .toolbarbutton-1 {
+toolbar[iconsize="small"] > #button-junk:hover {
-moz-image-region: rect(240px 39px 259px 20px);
}
-toolbar[iconsize="small"] > #button-junk > #junk-deck:hover:active > .toolbarbutton-1 {
+toolbar[iconsize="small"] > #button-junk:hover:active {
-moz-image-region: rect(240px 59px 259px 40px);
}
-toolbar[iconsize="small"] > #button-junk > #junk-deck[disabled="true"] > .toolbarbutton-1 {
+toolbar[iconsize="small"] > #button-junk[disabled="true"] {
-moz-image-region: rect(240px 79px 259px 60px) !important;
}
toolbar[iconsize="small"] > #button-print {
list-style-image: url("chrome://communicator/skin/icons/communicatoricons-small.png");
-moz-image-region: rect(0 19px 19px 0);
}
--- a/suite/themes/classic/navigator/navigator.css
+++ b/suite/themes/classic/navigator/navigator.css
@@ -39,20 +39,16 @@
@import url("chrome://navigator/content/navigator.css");
@import url("chrome://communicator/skin/");
@import url("chrome://communicator/skin/bookmarks/bookmarksToolbar.css");
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
/* ::::: primary toolbar buttons ::::: */
-.toolbarbutton-1 {
- list-style-image: url("chrome://navigator/skin/icons/navigatoricons.png");
-}
-
#back-button {
list-style-image: url("chrome://communicator/skin/icons/communicatoricons.png");
-moz-image-region: rect(60px 29px 89px 0);
}
#back-button:hover {
-moz-image-region: rect(60px 59px 89px 30px);
}
@@ -482,16 +478,20 @@ toolbar[mode="icons"] #search-button > .
#security-button[label] > .statusbarpanel-icon,
#security-button > .statusbarpanel-text {
margin: 0px;
background-color: #62C441;
color: #FFFFFF;
}
+#ev-button {
+ list-style-image: url("chrome://communicator/skin/icons/identity.png");
+}
+
#popupIcon {
list-style-image: url("chrome://navigator/skin/icons/popup-blocked.png");
}
/* ::::: personal toolbar ::::: */
#bookmarks-button {
list-style-image: url("chrome://communicator/skin/bookmarks/bookmark-folder-closed.png");
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..dc35c2c6fe1c2de14cdfcfe13196716af1d12dcd
GIT binary patch
literal 616
zc$@)f0+;=XP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0006nNkl<ZILoDx
zZA(*O7{~ntz3^-F2?`8GAq5S~Rm{SuPY@_YQnqOgG8!tbGF0Xvnv4b8GR=l(x%IG|
z|Fw?p!NE3M?Chboxu>(W=(Ns#S7&0r2z>Fox$ggUUB4Ii-PUY2+x7!JzKPWX)-TsA
zv-}0@#3(VH=*865Maqm+lbp=f#ZtR0c$Vt7@bwKuDo!k9Z(}igM_N>4D$#?r^ee=3
zF$&Tw%}CG8U$M!8$=F%&wIHM#?{f_7ycQ&T6g>xP!~l->QCtZ-CZ&w@2(rMi*^Q(S
zpn}z}W>`Pp0?7=UDtIzxh@wc@Dh6N(tN!%5TY~XG7u;zh#GgM9EU<VS?ghz4xziZ)
ztHEVnBUOrHKCb_rt)HTOS)lhTao?xJ9H++&*9ZMaCGMdM&jSpC$si>p!T7asMqYsV
zq@gpiV8nX@cR!p&v6=^$07hJwG3T2=qtTGrNWX!7ouRcWZU)(r1-f^gAeaI-P^e~+
z$!8%50#*|ax{%zCV%jqdp(fC~Gis*uvS7${41XE*oJS<<MJVHeZsiQND>*!TdmX7f
zk9xgMS&o?KtSlI071)V}J&GY`C$#KwFut4c##T_O7Adt_4UX_r3@$5Lf`O$D?8$-x
z4_GZ`-QyIaYXap;8B4(@7<k#y5?q}>j6Ja_=v(Y&QF=_$Z~X$>W9hRU!fyH{Ol!mc
zpl2^Z=TO;YLG^o~eax%2YurchKakmEIdo7K?1x`Jd)k)upZ-$-0000<MNUMnLSTY(
CuOCwY
--- a/suite/themes/modern/jar.mn
+++ b/suite/themes/modern/jar.mn
@@ -47,16 +47,17 @@ modern.jar:
skin/modern/communicator/icons/application.png (communicator/icons/application.png)
skin/modern/communicator/icons/audioFeedIcon.png (communicator/icons/feedIcon.png)
skin/modern/communicator/icons/btn1.gif (communicator/icons/btn1.gif)
skin/modern/communicator/icons/common.png (communicator/icons/common.png)
skin/modern/communicator/icons/common-small.png (communicator/icons/common-small.png)
skin/modern/communicator/icons/feedIcon.png (communicator/icons/feedIcon.png)
skin/modern/communicator/icons/feedIcon16.png (communicator/icons/feedIcon16.png)
skin/modern/communicator/icons/geo.png (communicator/icons/geo.png)
+ skin/modern/communicator/icons/identity.png (communicator/icons/identity.png)
skin/modern/communicator/icons/loading.gif (communicator/icons/loading.gif)
skin/modern/communicator/icons/lock-broken.png (communicator/icons/lock-broken.png)
skin/modern/communicator/icons/lock-insecure.png (communicator/icons/lock-insecure.png)
skin/modern/communicator/icons/lock-secure.png (communicator/icons/lock-secure.png)
skin/modern/communicator/icons/offline.gif (communicator/icons/offline.gif)
skin/modern/communicator/icons/online.gif (communicator/icons/online.gif)
skin/modern/communicator/icons/plugin.png (communicator/icons/plugin.png)
skin/modern/communicator/icons/save.png (communicator/icons/save.png)
--- a/suite/themes/modern/messenger/messageBody.css
+++ b/suite/themes/modern/messenger/messageBody.css
@@ -163,22 +163,22 @@ span.moz-txt-formfeed {
cursor: -moz-zoom-out;
}
.moz-attached-image[isshrunk="true"] {
cursor: -moz-zoom-in;
max-width: 100%;
}
-/* Style new format rss summary vs web page */
-body[selected="false"],
-iframe[selected="false"] {
+/* New style feed summary body. */
+body[selected="false"] {
display: none;
}
-
+
+/* Old style feeds. */
#_mailrssiframe {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
--- a/suite/themes/modern/messenger/primaryToolbar.css
+++ b/suite/themes/modern/messenger/primaryToolbar.css
@@ -47,17 +47,16 @@
#msgToolbar > .toolbar-holder > .toolbar-primary-icon {
list-style-image: url("chrome://messenger/skin/icons/mast-mail.gif");
}
/* ::::: primary toolbar buttons ::::: */
.toolbarbutton-1 {
- list-style-image: url("chrome://messenger/skin/icons/btn1.gif");
min-width: 0px !important;
}
#button-getmsg {
list-style-image: url("chrome://messenger/skin/icons/btn1.gif");
-moz-image-region: rect(102px 49px 135px 0);
}
@@ -207,21 +206,21 @@
}
#button-next[disabled] {
-moz-image-region: rect(204px 199px 237px 150px) !important;
}
#button-delete {
list-style-image: url("chrome://messenger/skin/icons/btn1.gif");
- -moz-image-region: rect(0, 49px 33px 0);
+ -moz-image-region: rect(0 49px 33px 0);
}
#button-delete:hover {
- -moz-image-region: rect(0, 99px 33px 50px);
+ -moz-image-region: rect(0 99px 33px 50px);
}
#button-delete:hover:active {
-moz-image-region: rect(0 149px 33px 100px);
}
#button-delete[disabled] {
-moz-image-region: rect(0 199px 33px 150px) !important;
--- a/suite/themes/modern/navigator/navigator.css
+++ b/suite/themes/modern/navigator/navigator.css
@@ -39,17 +39,16 @@
@import url("chrome://communicator/skin/");
@import url("chrome://communicator/skin/bookmarks/bookmarksToolbar.css");
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
/* ::::: primary toolbar buttons ::::: */
.toolbarbutton-1 {
- list-style-image: url("chrome://communicator/skin/icons/common.png");
min-width: 0px;
}
toolbox {
border-bottom: none;
}
#appcontent {
@@ -155,21 +154,16 @@ toolbox {
}
#home-button[disabled="true"] {
-moz-image-region: rect(156px 168px 195px 126px) !important;
}
/* ::::: small primary toolbar buttons ::::: */
-toolbar[iconsize="small"] > toolbarpaletteitem > .toolbarbutton-1,
-toolbar[iconsize="small"] > .toolbarbutton-1 {
- list-style-image: url("chrome://communicator/skin/icons/common-small.png");
-}
-
toolbar[iconsize="small"] > toolbarpaletteitem > #back-button,
toolbar[iconsize="small"] > #back-button {
list-style-image: url("chrome://communicator/skin/icons/common-small.png");
-moz-image-region: rect(38px 19px 57px 0);
}
toolbar[iconsize="small"] > toolbarpaletteitem > #back-button:hover,
toolbar[iconsize="small"] > #back-button:hover {
@@ -728,16 +722,20 @@ toolbarpaletteitem[place="palette"] > #p
#security-button[label] > .statusbarpanel-icon,
#security-button > .statusbarpanel-text {
margin: 0px;
background-color: #62C441;
color: #FFFFFF;
}
+#ev-button {
+ list-style-image: url("chrome://communicator/skin/icons/identity.png");
+}
+
#popupIcon {
list-style-image: url("chrome://navigator/skin/icons/popup-blocked.png");
}
/* ::::: feeds ::::: */
#feedsMenu {
list-style-image: url("chrome://navigator/skin/btn1/feeds.png") !important;